diff --git a/.config/nextest.toml b/.config/nextest.toml index 18bc23b5..ad7b9ec5 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,11 +1,14 @@ -[profile.ci] +[profile.default] +fail-fast = false +# Try up to 3 times with 10 second timeout each time. +# Helps detect deadlocks without hanging infinitely. +slow-timeout = { period = "10s", terminate-after = 3 } # Print out output for failing tests as soon as they fail, and also at the end # of the run (for easy scrollability). failure-output = "immediate-final" +status-level = "fail" -# Do not cancel the test run on the first failure. -fail-fast = false - -# Mark tests that take longer than 60s as slow. -# Terminate after 60s as a stop-gap measure to terminate on deadlock. -slow-timeout = { period = "60s", terminate-after = 1 } +[profile.ci] +inherits = "default" +retries = 0 +final-status-level = "slow" diff --git a/.gitattributes b/.gitattributes index 2fc638e7..9a5b8767 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,4 +2,3 @@ # Windows specific test files where we need CRLF endings crates/air/fixtures/crlf/*.R text eol=crlf -crates/air_r_parser/tests/snapshots/ok/crlf/*.R text eol=crlf diff --git a/.github/workflows/test-health.yml b/.github/workflows/test-health.yml index bf24a6dd..4725e6a7 100644 --- a/.github/workflows/test-health.yml +++ b/.github/workflows/test-health.yml @@ -20,8 +20,19 @@ jobs: - name: Rust cache uses: Swatinem/rust-cache@v2 + - name: Install just + uses: taiki-e/install-action@v2 + with: + tool: just + - name: Check formatting run: cargo fmt --all --check - name: Clippy run: cargo clippy --workspace --all-targets -- -D warnings + + - name: Codegen is up to date + run: just gen-grammar && git diff --exit-code + + - name: Parser markdown snapshots are up to date + run: cargo run -p xtask_accept && git diff --exit-code diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fcfc8e2..6f273c45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,35 @@ # Development version -- `for` loops with `...` or `..1` as the loop variable, like `for (... in x)`, are now supported. +- Air no longer utilizes [tree-sitter-r](https://github.com/r-lib/tree-sitter-r) for parsing R files. Instead, Air now ships with a completely custom R parser written entirely in Rust. This comes with a number of benefits! + + - Air is now roughly 2x faster. Formatting all of dplyr has gone from 300ms to 150ms on an M2 Mac. The actual parsing stage is around 7x faster. + + - Air now emits precise error messages when a file fails to parse due to syntax errors. + + - Air no longer has C in its dependency chain. This means that we'll be able to add WASM support for Air much easier now. + + If you are building a Rust based project that needs to parse R files, we encourage you to use our `air_r_parser` crate (pinned to a git SHA since we haven't put it on crates.io yet). + +- We've fixed a number of minor issues through the new parser: + + - Air more accurately accepts certain syntax: + + - `r"|...|"`, which are raw strings using the undocumented `|` delimiter + + - `%a\b%` where previously the `\` failed to parse + + - `for` loops with `...` or `..1` as the loop variable, like `for (... in x)` + + - Air more accurately rejects certain syntax errors: + + - `1 2`, which are two expressions side by side without a semicolon or newline + + - Stray top-level `;` + + - `pkg::` without a right-hand side + + - `x$1` where the right-hand side is not an identifier # 0.12.0 diff --git a/CLAUDE.md b/CLAUDE.md index 8e8ada76..6f7436ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -37,8 +37,11 @@ Air is an R language server and formatter written in Rust. Workspace crate layou ### Running Tests - Use `just test` to run tests via nextest, NOT `cargo test` -- Example: `just test -p workspace --lib discovery::test` -- Passes arguments through to `cargo nextest run` + - Example: `just test -p workspace --lib discovery::test` + - Passes arguments through to `cargo nextest run` +- `just test-snapshot-accept` regenerates the parser's markdown snapshots +- `just test-insta` regenerates the formatter's insta snapshots, `cargo insta review` to accept them +- `just test-quick-parser` and `just test-quick-formatter` run the `quick_test.rs` scratchpads ### Unit Tests diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 431ffb73..573c82b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -187,6 +187,6 @@ We use [nextest](https://nexte.st/) for testing rather than a standard `cargo te Install the nextest cli tool using a [prebuilt binary](https://nexte.st/docs/installation/pre-built-binaries/). -Run tests locally with `just test`, which calls `cargo nextest run`. Run insta snapshot tests in "update" mode with `just test-insta`. +Run tests locally with `just test`, which calls `cargo nextest run`. Run insta snapshot tests in "update" mode with `just test-insta`. Regenerate the parser's markdown snapshots with `just test-snapshot-accept`. -On CI we use the nextest profile found in `.config/nextest.toml`. +The nextest profiles are in `.config/nextest.toml`. Local runs use `default`, and CI uses `ci`. diff --git a/Cargo.lock b/Cargo.lock index fde560ed..410d4527 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,10 +87,8 @@ name = "air_r_formatter" version = "0.0.0" dependencies = [ "air_formatter_test", - "air_r_factory", "air_r_parser", "air_r_syntax", - "anyhow", "biome_formatter", "biome_parser", "biome_rowan", @@ -109,14 +107,24 @@ version = "0.0.0" dependencies = [ "air_r_factory", "air_r_syntax", + "biome_diagnostics", "biome_parser", "biome_rowan", - "biome_unicode_table", - "insta", - "tests_macros", - "tracing", - "tree-sitter", - "tree-sitter-r", + "unicode-ident", +] + +[[package]] +name = "air_r_parser_tests" +version = "0.0.0" +dependencies = [ + "air_r_parser", + "air_r_syntax", + "air_r_test_macros", + "biome_console", + "biome_diagnostics", + "biome_parser", + "biome_rowan", + "similar", ] [[package]] @@ -127,6 +135,14 @@ dependencies = [ "serde", ] +[[package]] +name = "air_r_test_macros" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "anstream" version = "0.6.17" @@ -1654,18 +1670,6 @@ dependencies = [ "syn 2.0.90", ] -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - [[package]] name = "regex-automata" version = "0.4.8" @@ -1963,12 +1967,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" - [[package]] name = "strsim" version = "0.11.1" @@ -2379,34 +2377,6 @@ dependencies = [ "tracing-log", ] -[[package]] -name = "tree-sitter" -version = "0.24.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75" -dependencies = [ - "cc", - "regex", - "regex-syntax", - "streaming-iterator", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-language" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ddffe35a0e5eeeadf13ff7350af564c6e73993a24db62caee1822b185c2600" - -[[package]] -name = "tree-sitter-r" -version = "1.2.0" -source = "git+https://github.com/r-lib/tree-sitter-r?rev=d459b97a9a11dd81643ab674938bede2874663b0#d459b97a9a11dd81643ab674938bede2874663b0" -dependencies = [ - "cc", - "tree-sitter-language", -] - [[package]] name = "typeid" version = "1.0.3" @@ -2824,6 +2794,14 @@ dependencies = [ "anyhow", ] +[[package]] +name = "xtask_accept" +version = "0.0.0" +dependencies = [ + "air_r_parser_tests", + "xtask", +] + [[package]] name = "xtask_codegen" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index bf7a40de..2ef291e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["crates/*", "editors/zed", "xtask/codegen", "xtask/json_schema"] +members = ["crates/*", "editors/zed", "xtask/accept", "xtask/codegen", "xtask/json_schema"] [workspace.package] authors = ["Posit Software, PBC"] @@ -18,9 +18,13 @@ air_formatter_test = { path = "./crates/air_formatter_test" } air_r_factory = { path = "./crates/air_r_factory" } air_r_formatter = { path = "./crates/air_r_formatter" } air_r_parser = { path = "./crates/air_r_parser" } +air_r_parser_tests = { path = "./crates/air_r_parser_tests" } air_r_syntax = { path = "./crates/air_r_syntax" } +air_r_test_macros = { path = "./crates/air_r_test_macros" } anyhow = "1.0.89" assert_matches = "1.5.0" +biome_console = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8b8d8f02181b2a4ce9315c5ef9" } +biome_diagnostics = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8b8d8f02181b2a4ce9315c5ef9" } biome_formatter = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8b8d8f02181b2a4ce9315c5ef9" } biome_line_index = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8b8d8f02181b2a4ce9315c5ef9" } biome_parser = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8b8d8f02181b2a4ce9315c5ef9" } @@ -28,7 +32,7 @@ biome_rowan = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8 biome_string_case = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8b8d8f02181b2a4ce9315c5ef9" } biome_text_size = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8b8d8f02181b2a4ce9315c5ef9" } biome_ungrammar = { path = "./crates/biome_ungrammar" } -biome_unicode_table = { git = "https://github.com/lionel-/biome", rev = "a1296ea6ba363d8b8d8f02181b2a4ce9315c5ef9" } +bpaf = { version = "0.9.15", features = ["derive"] } bytes = "1.8.0" cargo_metadata = "0.20.0" case = "1.0.0" @@ -42,6 +46,7 @@ etcetera = "0.11.0" fs = { path = "./crates/fs" } futures = "0.3.31" futures-util = "0.3.31" +git2 = { version = "0.21.0", default-features = false } globwalk = "0.9.1" httparse = "1.9.5" ignore = "0.4.23" @@ -60,6 +65,7 @@ schemars = "0.9.0" serde = "1.0.215" serde_json = "1.0.132" settings = { path = "./crates/settings" } +similar = "2.6" similar-asserts = "1.6.0" struct-field-names-as-array = "0.3.0" strum = "0.26" @@ -74,8 +80,7 @@ toml = "0.8.19" tower-lsp = { branch = "bugfix/patches", git = "https://github.com/lionel-/tower-lsp" } tracing = { version = "0.1.40", default-features = false, features = ["std"] } tracing-subscriber = "0.3.19" -tree-sitter = "0.24.7" -tree-sitter-r = { git = "https://github.com/r-lib/tree-sitter-r", rev = "d459b97a9a11dd81643ab674938bede2874663b0" } +unicode-ident = "1.0" url = "2.5.3" uuid = { version = "1.11.0", features = ["v4"] } workspace = { path = "./crates/workspace" } diff --git a/crates/air_r_formatter/Cargo.toml b/crates/air_r_formatter/Cargo.toml index a77be538..5edc1900 100644 --- a/crates/air_r_formatter/Cargo.toml +++ b/crates/air_r_formatter/Cargo.toml @@ -24,9 +24,7 @@ tracing = { workspace = true } [dev-dependencies] air_formatter_test = { workspace = true } -air_r_factory = { workspace = true } air_r_parser = { workspace = true } -anyhow = { workspace = true } biome_parser = { workspace = true } tests_macros = { workspace = true } workspace = { workspace = true } diff --git a/crates/air_r_formatter/src/r/auxiliary/empty_statement.rs b/crates/air_r_formatter/src/r/auxiliary/empty_statement.rs index c47f3916..9b3d9807 100644 --- a/crates/air_r_formatter/src/r/auxiliary/empty_statement.rs +++ b/crates/air_r_formatter/src/r/auxiliary/empty_statement.rs @@ -19,1188 +19,3 @@ impl FormatNodeRule for FormatREmptyStatement { write!(f, [format_removed(&semicolon_token?)]) } } - -// TODO(aether): Replace with `statements/semicolons.R` spec tests on the swap to aether -#[cfg(test)] -mod test { - use air_r_factory::r_argument; - use air_r_factory::r_argument_list; - use air_r_factory::r_braced_statements; - use air_r_factory::r_call; - use air_r_factory::r_call_arguments; - use air_r_factory::r_double_value; - use air_r_factory::r_empty_statement; - use air_r_factory::r_expression_statement; - use air_r_factory::r_function_definition; - use air_r_factory::r_identifier; - use air_r_factory::r_if_statement; - use air_r_factory::r_parameter_list; - use air_r_factory::r_parameters; - use air_r_factory::r_root; - use air_r_factory::r_statement_list; - use air_r_parser::RParserOptions; - use air_r_syntax::AnyRExpression; - use air_r_syntax::AnyRStatement; - use air_r_syntax::RRoot; - use air_r_syntax::RSyntaxKind; - use air_r_syntax::RSyntaxKind::*; - use air_r_syntax::RSyntaxToken; - use biome_rowan::AstNode; - use biome_rowan::TextSize; - use biome_rowan::TriviaPiece; - use biome_rowan::TriviaPieceKind; - - use crate::context::RFormatOptions; - use crate::format_node; - - #[derive(Clone, Copy)] - enum Trivia { - Newline, - Whitespace(&'static str), - Comment(&'static str), - } - - use Trivia::Comment; - use Trivia::Newline; - - const INDENT: Trivia = Trivia::Whitespace(" "); - const SPACE: Trivia = Trivia::Whitespace(" "); - - impl Trivia { - fn text(self) -> &'static str { - match self { - Trivia::Newline => "\n", - Trivia::Whitespace(text) | Trivia::Comment(text) => text, - } - } - - fn piece(self) -> TriviaPiece { - let kind = match self { - Trivia::Newline => TriviaPieceKind::Newline, - Trivia::Whitespace(_) => TriviaPieceKind::Whitespace, - Trivia::Comment(_) => TriviaPieceKind::SingleLineComment, - }; - TriviaPiece::new(kind, TextSize::of(self.text())) - } - } - - fn token( - kind: RSyntaxKind, - leading: &[Trivia], - text: &str, - trailing: &[Trivia], - ) -> RSyntaxToken { - let text: String = leading - .iter() - .map(|trivia| trivia.text()) - .chain([text]) - .chain(trailing.iter().map(|trivia| trivia.text())) - .collect(); - - RSyntaxToken::new_detached( - kind, - &text, - leading.iter().map(|trivia| trivia.piece()), - trailing.iter().map(|trivia| trivia.piece()), - ) - } - - fn root( - statements: impl IntoIterator, - ) -> RRoot { - r_root(r_statement_list(statements), token(EOF, &[], "", &[])).build() - } - - fn braces( - l_curly_token: RSyntaxToken, - statements: impl IntoIterator, - r_curly_token: RSyntaxToken, - ) -> AnyRExpression { - r_braced_statements(l_curly_token, r_statement_list(statements), r_curly_token).into() - } - - fn statement(expression: AnyRExpression) -> AnyRStatement { - r_expression_statement(expression).build().into() - } - - fn statement_with_semicolon( - expression: AnyRExpression, - semicolon_token: RSyntaxToken, - ) -> AnyRStatement { - r_expression_statement(expression) - .with_semicolon_token(semicolon_token) - .build() - .into() - } - - fn empty(semicolon_token: RSyntaxToken) -> AnyRStatement { - r_empty_statement(semicolon_token).into() - } - - fn double(value_token: RSyntaxToken) -> AnyRExpression { - AnyRExpression::AnyRValue(r_double_value(value_token).into()) - } - - fn identifier(name_token: RSyntaxToken) -> AnyRExpression { - r_identifier(name_token).into() - } - - fn call(name_token: RSyntaxToken, value: AnyRExpression) -> AnyRExpression { - let argument = r_argument().with_value(value).build(); - let arguments = r_call_arguments( - token(L_PAREN, &[], "(", &[]), - r_argument_list([argument], []), - token(R_PAREN, &[], ")", &[]), - ); - r_call(identifier(name_token), arguments).into() - } - - /// Format a hand-built `root` - /// - /// Checks that `root` was transcribed from `source` correctly, and that the - /// output is stable when parsed and formatted again. - fn format(root: RRoot, source: &str) -> anyhow::Result { - assert_eq!(root.syntax().to_string(), source); - - let output = format_node(RFormatOptions::default(), root.syntax())? - .print()? - .into_code(); - - // tree-sitter hides `;`, so we can't parse a kept `;` back - if output.contains(';') { - return Ok(output); - } - - let reparsed = air_r_parser::parse(&output, RParserOptions::default()); - assert!(!reparsed.has_errors()); - let reformatted = format_node(RFormatOptions::default(), &reparsed.syntax())? - .print()? - .into_code(); - assert_eq!(output, reformatted); - - Ok(output) - } - - #[test] - fn test_semicolons_between_statements() -> anyhow::Result<()> { - let source = "\ -{ - 1;; - 2 -}"; - let expected = "\ -{ - 1 - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement_with_semicolon( - double(token(R_DOUBLE_LITERAL, &[Newline, INDENT], "1", &[])), - token(SEMICOLON, &[], ";", &[]), - ), - empty(token(SEMICOLON, &[], ";", &[])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_braces_with_only_semicolons() -> anyhow::Result<()> { - let source = "\ -{;}"; - let expected = "\ -{} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [empty(token(SEMICOLON, &[], ";", &[]))], - token(R_CURLY, &[], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{;;}"; - let expected = "\ -{} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - empty(token(SEMICOLON, &[], ";", &[])), - empty(token(SEMICOLON, &[], ";", &[])), - ], - token(R_CURLY, &[], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - Ok(()) - } - - #[test] - fn test_empty_line_before_lone_semicolon_is_kept() -> anyhow::Result<()> { - let source = "\ -{ - 1 - - ; - 2 -}"; - let expected = "\ -{ - 1 - - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token(SEMICOLON, &[Newline, Newline, INDENT], ";", &[])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_empty_line_before_leading_lone_semicolon_is_removed() -> anyhow::Result<()> { - let source = "\ -{ - - ; - 1 -}"; - let expected = "\ -{ - 1 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - empty(token(SEMICOLON, &[Newline, Newline, INDENT], ";", &[])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_lone_semicolon_does_not_add_empty_line() -> anyhow::Result<()> { - let source = "\ -{ - 1 - ; - 2 -}"; - let expected = "\ -{ - 1 - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_comment_before_lone_semicolon() -> anyhow::Result<()> { - let source = "\ -{ - 1 # c - ; -}"; - let expected = "\ -{ - 1 # c -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[SPACE, Comment("# c")], - ))), - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[])), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{1 # c -;}"; - let expected = "\ -{ - 1 # c -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[], - "1", - &[SPACE, Comment("# c")], - ))), - empty(token(SEMICOLON, &[Newline], ";", &[])), - ], - token(R_CURLY, &[], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - Ok(()) - } - - #[test] - fn test_comment_after_lone_semicolon_trails_previous_statement() -> anyhow::Result<()> { - let source = "\ -{ - 1 - ; # c -}"; - let expected = "\ -{ - 1 # c -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token( - SEMICOLON, - &[Newline, INDENT], - ";", - &[SPACE, Comment("# c")], - )), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_comment_after_lone_semicolon_leads_next_statement() -> anyhow::Result<()> { - let source = "\ -{ - ; # c - 1 -}"; - let expected = "\ -{ - # c - 1 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - empty(token( - SEMICOLON, - &[Newline, INDENT], - ";", - &[SPACE, Comment("# c")], - )), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_comment_after_lone_semicolon_dangles() -> anyhow::Result<()> { - let source = "\ -{;# c -}"; - let expected = "\ -{ - # c -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [empty(token(SEMICOLON, &[], ";", &[Comment("# c")]))], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_comments_around_lone_semicolon_dangle() -> anyhow::Result<()> { - let source = "\ -{ - # a - ; # b - # c -}"; - let expected = "\ -{ - # a - # b - # c -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [empty(token( - SEMICOLON, - &[Newline, INDENT, Comment("# a"), Newline, INDENT], - ";", - &[SPACE, Comment("# b")], - ))], - token( - R_CURLY, - &[Newline, INDENT, Comment("# c"), Newline], - "}", - &[], - ), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_comments_around_lone_semicolon_keep_order() -> anyhow::Result<()> { - let source = "\ -{ - 1 - # a - ; # b - 2 -}"; - let expected = "\ -{ - 1 - # a - # b - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token( - SEMICOLON, - &[Newline, INDENT, Comment("# a"), Newline, INDENT], - ";", - &[SPACE, Comment("# b")], - )), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - /// Handles every variant of [crate::comments::handle_empty_statement_comment] - #[test] - fn test_handle_empty_statement_comment() -> anyhow::Result<()> { - const TRAILS: Trivia = Comment("# Trails preceding"); - const LEADS: Trivia = Comment("# Leads following"); - const DANGLING: Trivia = Comment("# Dangling on enclosing"); - - // `CommentTextPosition::EndOfLine` - - let source = "\ -{ - 1; ; # Trails preceding - 2 -}"; - let expected = "\ -{ - 1 # Trails preceding - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement_with_semicolon( - double(token(R_DOUBLE_LITERAL, &[Newline, INDENT], "1", &[])), - token(SEMICOLON, &[], ";", &[]), - ), - empty(token(SEMICOLON, &[SPACE], ";", &[SPACE, TRAILS])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - 1; ; ; # Trails preceding - 2 -}"; - let expected = "\ -{ - 1 # Trails preceding - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement_with_semicolon( - double(token(R_DOUBLE_LITERAL, &[Newline, INDENT], "1", &[])), - token(SEMICOLON, &[], ";", &[]), - ), - empty(token(SEMICOLON, &[SPACE], ";", &[])), - empty(token(SEMICOLON, &[SPACE], ";", &[SPACE, TRAILS])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - 1 - ; ; # Leads following - 2 -}"; - let expected = "\ -{ - 1 - # Leads following - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[])), - empty(token(SEMICOLON, &[SPACE], ";", &[SPACE, LEADS])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - 1 - ; # Leads following - 2 -}"; - let expected = "\ -{ - 1 - # Leads following - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[SPACE, LEADS])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - 1 - ; # Trails preceding -}"; - let expected = "\ -{ - 1 # Trails preceding -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[SPACE, TRAILS])), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - ; # Leads following - 2 -}"; - let expected = "\ -{ - # Leads following - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[SPACE, LEADS])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - ; # Dangling on enclosing -}"; - let expected = "\ -{ - # Dangling on enclosing -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [empty(token( - SEMICOLON, - &[Newline, INDENT], - ";", - &[SPACE, DANGLING], - ))], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - // `CommentTextPosition::OwnLine` - - let source = "\ -{ - 1 - # Leads following - ; - 2 -}"; - let expected = "\ -{ - 1 - # Leads following - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token( - SEMICOLON, - &[Newline, INDENT, LEADS, Newline, INDENT], - ";", - &[], - )), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - 1 - ; - # Leads following - 2 -}"; - let expected = "\ -{ - 1 - # Leads following - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT, LEADS, Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - # Leads following - ; - 2 -}"; - let expected = "\ -{ - # Leads following - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - empty(token( - SEMICOLON, - &[Newline, INDENT, LEADS, Newline, INDENT], - ";", - &[], - )), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - ; - # Leads following - 2 -}"; - let expected = "\ -{ - # Leads following - 2 -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT, LEADS, Newline, INDENT], - "2", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - 1 - # Trails preceding - ; -}"; - let expected = "\ -{ - 1 - # Trails preceding -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token( - SEMICOLON, - &[Newline, INDENT, TRAILS, Newline, INDENT], - ";", - &[], - )), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - 1 - ; - # Trails preceding -}"; - let expected = "\ -{ - 1 - # Trails preceding -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[])), - ], - token(R_CURLY, &[Newline, INDENT, TRAILS, Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - # Dangling on enclosing - ; -}"; - let expected = "\ -{ - # Dangling on enclosing -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [empty(token( - SEMICOLON, - &[Newline, INDENT, DANGLING, Newline, INDENT], - ";", - &[], - ))], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -{ - ; - # Dangling on enclosing -}"; - let expected = "\ -{ - # Dangling on enclosing -} -"; - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [empty(token(SEMICOLON, &[Newline, INDENT], ";", &[]))], - token(R_CURLY, &[Newline, INDENT, DANGLING, Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - - Ok(()) - } - - #[test] - fn test_function_body_comment_skips_lone_semicolon() -> anyhow::Result<()> { - fn function(body: AnyRExpression) -> AnyRExpression { - let parameters = r_parameters( - token(L_PAREN, &[], "(", &[]), - r_parameter_list([], []), - token(R_PAREN, &[], ")", &[SPACE, Comment("# c")]), - ); - r_function_definition(token(FUNCTION_KW, &[], "function", &[]), parameters, body).into() - } - - let source = "\ -function() # c -{ - ; - 1 -}"; - let expected = "\ -function() { - # c - 1 -} -"; - let tree = root([statement(function(braces( - token(L_CURLY, &[Newline], "{", &[]), - [ - empty(token(SEMICOLON, &[Newline, INDENT], ";", &[])), - statement(double(token( - R_DOUBLE_LITERAL, - &[Newline, INDENT], - "1", - &[], - ))), - ], - token(R_CURLY, &[Newline], "}", &[]), - )))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -function() # c -{;}"; - let expected = "\ -function() { - # c -} -"; - let tree = root([statement(function(braces( - token(L_CURLY, &[Newline], "{", &[]), - [empty(token(SEMICOLON, &[], ";", &[]))], - token(R_CURLY, &[], "}", &[]), - )))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -function() # c -{ - ; -}"; - let expected = "\ -function() { - # c -} -"; - let tree = root([statement(function(braces( - token(L_CURLY, &[Newline], "{", &[]), - [empty(token(SEMICOLON, &[Newline, INDENT], ";", &[]))], - token(R_CURLY, &[Newline], "}", &[]), - )))]); - assert_eq!(format(tree, source)?, expected); - - Ok(()) - } - - #[test] - fn test_trailing_semicolons_keep_value_position() -> anyhow::Result<()> { - let source = "\ -{ - if (a) 1;; -}"; - let expected = "\ -{ - if (a) 1 -} -"; - let if_statement = r_if_statement( - token(IF_KW, &[Newline, INDENT], "if", &[SPACE]), - token(L_PAREN, &[], "(", &[]), - identifier(token(IDENT, &[], "a", &[])), - token(R_PAREN, &[], ")", &[SPACE]), - double(token(R_DOUBLE_LITERAL, &[], "1", &[])), - ) - .build(); - let tree = root([statement(braces( - token(L_CURLY, &[], "{", &[]), - [ - statement_with_semicolon(if_statement.into(), token(SEMICOLON, &[], ";", &[])), - empty(token(SEMICOLON, &[], ";", &[])), - ], - token(R_CURLY, &[Newline], "}", &[]), - ))]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_curly_curly_removes_semicolon() -> anyhow::Result<()> { - let source = "\ -fn({{ x }; })"; - let expected = "\ -fn({{ x }}) -"; - let inner = braces( - token(L_CURLY, &[], "{", &[SPACE]), - [statement(identifier(token(IDENT, &[], "x", &[SPACE])))], - token(R_CURLY, &[], "}", &[]), - ); - let outer = braces( - token(L_CURLY, &[], "{", &[]), - [statement_with_semicolon( - inner, - token(SEMICOLON, &[], ";", &[SPACE]), - )], - token(R_CURLY, &[], "}", &[]), - ); - let tree = root([statement(call(token(IDENT, &[], "fn", &[]), outer))]); - assert_eq!(format(tree, source)?, expected); - - let source = "\ -fn({{ x; }})"; - let expected = "\ -fn({{ x }}) -"; - let inner = braces( - token(L_CURLY, &[], "{", &[SPACE]), - [statement_with_semicolon( - identifier(token(IDENT, &[], "x", &[])), - token(SEMICOLON, &[], ";", &[SPACE]), - )], - token(R_CURLY, &[], "}", &[]), - ); - let outer = braces( - token(L_CURLY, &[], "{", &[]), - [statement(inner)], - token(R_CURLY, &[], "}", &[]), - ); - let tree = root([statement(call(token(IDENT, &[], "fn", &[]), outer))]); - assert_eq!(format(tree, source)?, expected); - - Ok(()) - } - - #[test] - fn test_skip_keeps_semicolon() -> anyhow::Result<()> { - let source = "\ -# fmt: skip -x;"; - let expected = "\ -# fmt: skip -x; -"; - let tree = root([statement_with_semicolon( - identifier(token(IDENT, &[Comment("# fmt: skip"), Newline], "x", &[])), - token(SEMICOLON, &[], ";", &[]), - )]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } - - #[test] - fn test_comment_after_semicolon() -> anyhow::Result<()> { - let source = "\ -1; # c"; - let expected = "\ -1 # c -"; - let tree = root([statement_with_semicolon( - double(token(R_DOUBLE_LITERAL, &[], "1", &[])), - token(SEMICOLON, &[], ";", &[SPACE, Comment("# c")]), - )]); - assert_eq!(format(tree, source)?, expected); - Ok(()) - } -} diff --git a/crates/air_r_formatter/tests/specs/r/binary_expression.R b/crates/air_r_formatter/tests/specs/r/binary_expression.R index 6a7974e5..7ef7152b 100644 --- a/crates/air_r_formatter/tests/specs/r/binary_expression.R +++ b/crates/air_r_formatter/tests/specs/r/binary_expression.R @@ -42,6 +42,7 @@ argument_that_is_quite_long + argument_that_is_quite_long^argument_that_is_quite 1 / 2 1 |> 2 1 %>% 2 +1 %a\b% 2 # The following expressions should not have spaces 1 ? 2 diff --git a/crates/air_r_formatter/tests/specs/r/binary_expression.R.snap b/crates/air_r_formatter/tests/specs/r/binary_expression.R.snap index 90b4784f..b5413b73 100644 --- a/crates/air_r_formatter/tests/specs/r/binary_expression.R.snap +++ b/crates/air_r_formatter/tests/specs/r/binary_expression.R.snap @@ -49,6 +49,7 @@ argument_that_is_quite_long + argument_that_is_quite_long^argument_that_is_quite 1 / 2 1 |> 2 1 %>% 2 +1 %a\b% 2 # The following expressions should not have spaces 1 ? 2 @@ -753,6 +754,7 @@ argument_that_is_quite_long + 1 / 2 1 |> 2 1 %>% 2 +1 %a\b% 2 # The following expressions should not have spaces 1?2 @@ -1524,17 +1526,17 @@ y ~ x1 + # Lines exceeding max width of 80 characters ``` - 206: # TODO: Do we really want these chained? It seems like no good can come of that even though they have same precedence - 582: a_really_really_really_really_really_really_long_thing_here ~ a_really_really_really_really_really_really_long_thing_here2 - 583: a_really_really_really_really_really_really_long_thing_here = a_really_really_really_really_really_really_long_thing_here2 - 584: a_really_really_really_really_really_really_long_thing_here := a_really_really_really_really_really_really_long_thing_here2 - 585: a_really_really_really_really_really_really_long_thing_here <- a_really_really_really_really_really_really_long_thing_here2 - 586: a_really_really_really_really_really_really_long_thing_here -> a_really_really_really_really_really_really_long_thing_here2 - 587: a_really_really_really_really_really_really_long_thing_here <<- a_really_really_really_really_really_really_long_thing_here2 - 588: a_really_really_really_really_really_really_long_thing_here ->> a_really_really_really_really_really_really_long_thing_here2 - 700: a_really_really_long_thing_here1 ~ a_really_really_long_thing_here2 ~ a_really_really_long_thing_here3 - 701: a_really_really_long_thing_here1 ~ a_really_really_really_really_really_really_long_thing_here2 ~ a_really_really_long_thing_here3 - 702: (a_really_really_long_thing_here1 ~ a_really_really_long_thing_here2) ~ a_really_really_long_thing_here3 - 703: a_really_really_long_thing_here1 ~ (a_really_really_long_thing_here2 ~ a_really_really_long_thing_here3) - 756: # https://github.com/posit-dev/positron/discussions/6095#discussioncomment-13112983 + 207: # TODO: Do we really want these chained? It seems like no good can come of that even though they have same precedence + 583: a_really_really_really_really_really_really_long_thing_here ~ a_really_really_really_really_really_really_long_thing_here2 + 584: a_really_really_really_really_really_really_long_thing_here = a_really_really_really_really_really_really_long_thing_here2 + 585: a_really_really_really_really_really_really_long_thing_here := a_really_really_really_really_really_really_long_thing_here2 + 586: a_really_really_really_really_really_really_long_thing_here <- a_really_really_really_really_really_really_long_thing_here2 + 587: a_really_really_really_really_really_really_long_thing_here -> a_really_really_really_really_really_really_long_thing_here2 + 588: a_really_really_really_really_really_really_long_thing_here <<- a_really_really_really_really_really_really_long_thing_here2 + 589: a_really_really_really_really_really_really_long_thing_here ->> a_really_really_really_really_really_really_long_thing_here2 + 701: a_really_really_long_thing_here1 ~ a_really_really_long_thing_here2 ~ a_really_really_long_thing_here3 + 702: a_really_really_long_thing_here1 ~ a_really_really_really_really_really_really_long_thing_here2 ~ a_really_really_long_thing_here3 + 703: (a_really_really_long_thing_here1 ~ a_really_really_long_thing_here2) ~ a_really_really_long_thing_here3 + 704: a_really_really_long_thing_here1 ~ (a_really_really_long_thing_here2 ~ a_really_really_long_thing_here3) + 757: # https://github.com/posit-dev/positron/discussions/6095#discussioncomment-13112983 ``` diff --git a/crates/air_r_formatter/tests/specs/r/semicolons.R b/crates/air_r_formatter/tests/specs/r/semicolons.R new file mode 100644 index 00000000..bdc8ab8f --- /dev/null +++ b/crates/air_r_formatter/tests/specs/r/semicolons.R @@ -0,0 +1,238 @@ +# ------------------------------------------------------------------------ +# Semicolons between statements + +1;2 + +1; + +{1;;2} + +{;1} + +{ + 1;; + 2 +} + +# Trailing semicolons keep value position +{ + if (a) 1;; +} + +# ------------------------------------------------------------------------ +# Braces with only semicolons + +{;} + +{;;} + +# ------------------------------------------------------------------------ +# Empty lines around a lone semicolon + +# Empty line before lone semicolon is kept +{ + 1 + + ; + 2 +} + +# Empty line after lone semicolon is kept +{ + 1 + ; + + 2 +} + +# Empty line before leading lone semicolon is removed +{ + + ; + 1 +} + +# Lone semicolon does not add empty line +{ + 1 + ; + 2 +} + +# ------------------------------------------------------------------------ +# Comments after a semicolon + +1; # c + +1 ; # c + +# ------------------------------------------------------------------------ +# Comment before a lone semicolon + +{ + 1 # c + ; +} + +{1 # c +;} + +# ------------------------------------------------------------------------ +# Comment after a lone semicolon + +# Trails previous statement +{ + 1 + ; # c +} + +# Leads next statement +{ + ; # c + 1 +} + +# Dangles +{;# c +} + +# ------------------------------------------------------------------------ +# Comments around a lone semicolon + +# Dangle +{ + # a + ; # b + # c +} + +# Keep order +{ + 1 + # a + ; # b + 2 +} + +# ------------------------------------------------------------------------ +# Every variant of `handle_empty_statement_comment()`, end of line + +{ + 1; ; # Trails preceding + 2 +} + +{ + 1; ; ; # Trails preceding + 2 +} + +{ + 1 + ; ; # Leads following + 2 +} + +{ + 1 + ; # Leads following + 2 +} + +{ + 1 + ; # Trails preceding +} + +{ + ; # Leads following + 2 +} + +{ + ; # Dangling on enclosing +} + +# ------------------------------------------------------------------------ +# Every variant of `handle_empty_statement_comment()`, own line + +{ + 1 + # Leads following + ; + 2 +} + +{ + 1 + ; + # Leads following + 2 +} + +{ + # Leads following + ; + 2 +} + +{ + ; + # Leads following + 2 +} + +{ + 1 + # Trails preceding + ; +} + +{ + 1 + ; + # Trails preceding +} + +{ + # Dangling on enclosing + ; +} + +{ + ; + # Dangling on enclosing +} + +# ------------------------------------------------------------------------ +# Function body comment skips semicolons + +# Due to `place_leading_or_dangling_body_comment()` finding the first +# non-empy statement, and placing the comment as leading on that. + +function() # Leads 1 +{ + ; + 1 +} + +function() # Dangles braces +{;} + +function() # Dangles braces +{ + ; +} + +# ------------------------------------------------------------------------ +# Curly-curly removes semicolon + +fn({{ x }; }) + +fn({{ x; }}) + +# ------------------------------------------------------------------------ +# `# fmt: skip` keeps semicolon + +# fmt: skip +x; diff --git a/crates/air_r_formatter/tests/specs/r/semicolons.R.snap b/crates/air_r_formatter/tests/specs/r/semicolons.R.snap new file mode 100644 index 00000000..514d8472 --- /dev/null +++ b/crates/air_r_formatter/tests/specs/r/semicolons.R.snap @@ -0,0 +1,496 @@ +--- +source: crates/air_formatter_test/src/snapshot_builder.rs +info: r/semicolons.R +--- +# Input + +```R +# ------------------------------------------------------------------------ +# Semicolons between statements + +1;2 + +1; + +{1;;2} + +{;1} + +{ + 1;; + 2 +} + +# Trailing semicolons keep value position +{ + if (a) 1;; +} + +# ------------------------------------------------------------------------ +# Braces with only semicolons + +{;} + +{;;} + +# ------------------------------------------------------------------------ +# Empty lines around a lone semicolon + +# Empty line before lone semicolon is kept +{ + 1 + + ; + 2 +} + +# Empty line after lone semicolon is kept +{ + 1 + ; + + 2 +} + +# Empty line before leading lone semicolon is removed +{ + + ; + 1 +} + +# Lone semicolon does not add empty line +{ + 1 + ; + 2 +} + +# ------------------------------------------------------------------------ +# Comments after a semicolon + +1; # c + +1 ; # c + +# ------------------------------------------------------------------------ +# Comment before a lone semicolon + +{ + 1 # c + ; +} + +{1 # c +;} + +# ------------------------------------------------------------------------ +# Comment after a lone semicolon + +# Trails previous statement +{ + 1 + ; # c +} + +# Leads next statement +{ + ; # c + 1 +} + +# Dangles +{;# c +} + +# ------------------------------------------------------------------------ +# Comments around a lone semicolon + +# Dangle +{ + # a + ; # b + # c +} + +# Keep order +{ + 1 + # a + ; # b + 2 +} + +# ------------------------------------------------------------------------ +# Every variant of `handle_empty_statement_comment()`, end of line + +{ + 1; ; # Trails preceding + 2 +} + +{ + 1; ; ; # Trails preceding + 2 +} + +{ + 1 + ; ; # Leads following + 2 +} + +{ + 1 + ; # Leads following + 2 +} + +{ + 1 + ; # Trails preceding +} + +{ + ; # Leads following + 2 +} + +{ + ; # Dangling on enclosing +} + +# ------------------------------------------------------------------------ +# Every variant of `handle_empty_statement_comment()`, own line + +{ + 1 + # Leads following + ; + 2 +} + +{ + 1 + ; + # Leads following + 2 +} + +{ + # Leads following + ; + 2 +} + +{ + ; + # Leads following + 2 +} + +{ + 1 + # Trails preceding + ; +} + +{ + 1 + ; + # Trails preceding +} + +{ + # Dangling on enclosing + ; +} + +{ + ; + # Dangling on enclosing +} + +# ------------------------------------------------------------------------ +# Function body comment skips semicolons + +# Due to `place_leading_or_dangling_body_comment()` finding the first +# non-empy statement, and placing the comment as leading on that. + +function() # Leads 1 +{ + ; + 1 +} + +function() # Dangles braces +{;} + +function() # Dangles braces +{ + ; +} + +# ------------------------------------------------------------------------ +# Curly-curly removes semicolon + +fn({{ x }; }) + +fn({{ x; }}) + +# ------------------------------------------------------------------------ +# `# fmt: skip` keeps semicolon + +# fmt: skip +x; + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Space +Indent width: 2 +Line ending: LF +Line width: 80 +Persistent line breaks: Respect +Assignment style: Arrow +Table: fcase, rowwiseDT, tribble +----- + +```R +# ------------------------------------------------------------------------ +# Semicolons between statements + +1 +2 + +1 + +{ + 1 + 2 +} + +{ + 1 +} + +{ + 1 + 2 +} + +# Trailing semicolons keep value position +{ + if (a) 1 +} + +# ------------------------------------------------------------------------ +# Braces with only semicolons + +{} + +{} + +# ------------------------------------------------------------------------ +# Empty lines around a lone semicolon + +# Empty line before lone semicolon is kept +{ + 1 + + 2 +} + +# Empty line after lone semicolon is kept +{ + 1 + + 2 +} + +# Empty line before leading lone semicolon is removed +{ + 1 +} + +# Lone semicolon does not add empty line +{ + 1 + 2 +} + +# ------------------------------------------------------------------------ +# Comments after a semicolon + +1 # c + +1 # c + +# ------------------------------------------------------------------------ +# Comment before a lone semicolon + +{ + 1 # c +} + +{ + 1 # c +} + +# ------------------------------------------------------------------------ +# Comment after a lone semicolon + +# Trails previous statement +{ + 1 # c +} + +# Leads next statement +{ + # c + 1 +} + +# Dangles +{ + # c +} + +# ------------------------------------------------------------------------ +# Comments around a lone semicolon + +# Dangle +{ + # a + # b + # c +} + +# Keep order +{ + 1 + # a + # b + 2 +} + +# ------------------------------------------------------------------------ +# Every variant of `handle_empty_statement_comment()`, end of line + +{ + 1 # Trails preceding + 2 +} + +{ + 1 # Trails preceding + 2 +} + +{ + 1 + # Leads following + 2 +} + +{ + 1 + # Leads following + 2 +} + +{ + 1 # Trails preceding +} + +{ + # Leads following + 2 +} + +{ + # Dangling on enclosing +} + +# ------------------------------------------------------------------------ +# Every variant of `handle_empty_statement_comment()`, own line + +{ + 1 + # Leads following + 2 +} + +{ + 1 + # Leads following + 2 +} + +{ + # Leads following + 2 +} + +{ + # Leads following + 2 +} + +{ + 1 + # Trails preceding +} + +{ + 1 + # Trails preceding +} + +{ + # Dangling on enclosing +} + +{ + # Dangling on enclosing +} + +# ------------------------------------------------------------------------ +# Function body comment skips semicolons + +# Due to `place_leading_or_dangling_body_comment()` finding the first +# non-empy statement, and placing the comment as leading on that. + +function() { + # Leads 1 + 1 +} + +function() { + # Dangles braces +} + +function() { + # Dangles braces +} + +# ------------------------------------------------------------------------ +# Curly-curly removes semicolon + +fn({{ x }}) + +fn({{ x }}) + +# ------------------------------------------------------------------------ +# `# fmt: skip` keeps semicolon + +# fmt: skip +x; +``` diff --git a/crates/air_r_formatter/tests/specs/r/value/string_value.R b/crates/air_r_formatter/tests/specs/r/value/string_value.R index ddf6c14a..19fd4e3f 100644 --- a/crates/air_r_formatter/tests/specs/r/value/string_value.R +++ b/crates/air_r_formatter/tests/specs/r/value/string_value.R @@ -7,6 +7,8 @@ string" r"("some raw string business")" +r"|some raw string|" +r"-|some raw string|-" # ------------------------------------------------------------------------ # Comments diff --git a/crates/air_r_formatter/tests/specs/r/value/string_value.R.snap b/crates/air_r_formatter/tests/specs/r/value/string_value.R.snap index 3a3f57ab..b99f3714 100644 --- a/crates/air_r_formatter/tests/specs/r/value/string_value.R.snap +++ b/crates/air_r_formatter/tests/specs/r/value/string_value.R.snap @@ -14,6 +14,8 @@ info: r/value/string_value.R string" r"("some raw string business")" +r"|some raw string|" +r"-|some raw string|-" # ------------------------------------------------------------------------ # Comments @@ -61,6 +63,8 @@ Table: fcase, rowwiseDT, tribble string" r"("some raw string business")" +r"|some raw string|" +r"-|some raw string|-" # ------------------------------------------------------------------------ # Comments diff --git a/crates/air_r_parser/CLAUDE.md b/crates/air_r_parser/CLAUDE.md new file mode 100644 index 00000000..c04b6cc5 --- /dev/null +++ b/crates/air_r_parser/CLAUDE.md @@ -0,0 +1,54 @@ +# air_r_parser + +A hand-written Rust parser for R, built on Biome's parser infrastructure. + +## Invariants, do not break these + +1. `parse(src).syntax().to_string() == src` for ALL inputs. Always. + +2. Bogus nodes always come with at least one diagnostic. + +3. The parser never panics and never loops forever. + +## Test corpus + +The corpus lives in `crates/air_r_parser_tests/tests/corpus/**/*.md`. One file +per concept, one test per `##` heading. + +Each concept has two files. You write `X.md`, which holds headings, prose, and +\`\`\`r blocks: + +````md +## Colon + +```r +1:2 +``` + +## Closing brace [error] + +A stray `}` is reported and skipped, and the parse keeps going. + +```r +} +``` +```` + +`just test-snapshot-accept` generates `X.snap.md` from it. The snapshot repeats +the heading, the prose, and each source, and adds a cst and a diagnostics block +after each source. Never hand-edit a snapshot. To change a test, edit `X.md` and +re-run `just test-snapshot-accept`. + +Prose between blocks is copied to the snapshot and otherwise ignored. Use it to +say why a test exists. Do not use an R comment for that, since a comment is +input and changes the tree. + +The heading marker sets what the blocks under it must do: + +- `[error]`: each block produces at least one error diagnostic. + +- No marker: each block parses with zero diagnostics and no bogus nodes. + +## Reference implementations + +- Biome's JS/CSS parsers. When in doubt, do what `biome_js_parser` does. diff --git a/crates/air_r_parser/Cargo.toml b/crates/air_r_parser/Cargo.toml index fced5a63..5a566a40 100644 --- a/crates/air_r_parser/Cargo.toml +++ b/crates/air_r_parser/Cargo.toml @@ -12,23 +12,15 @@ keywords.workspace = true categories.workspace = true publish = false -# cargo-workspaces metadata -[package.metadata.workspaces] -independent = true - [dependencies] air_r_factory = { workspace = true } air_r_syntax = { workspace = true } biome_parser = { workspace = true } biome_rowan = { workspace = true } -biome_unicode_table = { workspace = true } -tracing = { workspace = true } -tree-sitter = { workspace = true } -tree-sitter-r = { workspace = true } +unicode-ident = { workspace = true } [dev-dependencies] -insta = { workspace = true } -tests_macros = { workspace = true } +biome_diagnostics = { workspace = true } [lints] workspace = true diff --git a/crates/air_r_parser/src/diagnostics.rs b/crates/air_r_parser/src/diagnostics.rs new file mode 100644 index 00000000..1466feef --- /dev/null +++ b/crates/air_r_parser/src/diagnostics.rs @@ -0,0 +1,223 @@ +//! Constructors for every diagnostic the parser emits + +use crate::parser::RParser; +use air_r_syntax::RSyntaxKind; +use biome_parser::Parser; +use biome_parser::diagnostic::ParseDiagnostic; +use biome_parser::token_source::TokenSource; +use biome_rowan::TextLen; +use biome_rowan::TextRange; +use std::borrow::Cow; + +/// A backtick-quoted name that runs to the end of the file +pub(crate) fn unterminated_backtick_identifier(open: TextRange, eof: TextRange) -> ParseDiagnostic { + ParseDiagnostic::new("Unterminated backtick-quoted name.", open) + .with_detail(eof, "Reached the end of the file looking for a closing `.") +} + +/// A `%...%` operator cut short by a newline or the end of the file +pub(crate) fn unterminated_special(open: TextRange) -> ParseDiagnostic { + ParseDiagnostic::new("Unterminated `%` operator.", open) + .with_hint("A `%...%` operator must be closed by a second `%` on the same line.") +} + +/// A `0x` with no body, as in `0x` or `0xg` +pub(crate) fn unterminated_hexadecimal(range: TextRange) -> ParseDiagnostic { + ParseDiagnostic::new("Unterminated hexadecimal literal.", range) + .with_hint("`0x` must be followed by hexadecimal digits, a `.`, or both.") +} + +/// A `p` exponent with no digits, as in `0x1p` or `0x1p+` +pub(crate) fn unterminated_hexadecimal_binary_exponent(range: TextRange) -> ParseDiagnostic { + ParseDiagnostic::new("Unterminated binary exponent.", range) + .with_hint("A `p` exponent must be followed by decimal digits.") +} + +/// An `e` exponent with no digits, as in `1e` or `1e+` +pub(crate) fn unterminated_exponent(range: TextRange) -> ParseDiagnostic { + ParseDiagnostic::new("Unterminated exponent.", range) + .with_hint("An `e` exponent must be followed by decimal digits.") +} + +/// A character that starts no known R token +pub(crate) fn unexpected_character(range: TextRange, character: char) -> ParseDiagnostic { + ParseDiagnostic::new(format!("Unexpected character `{character}`."), range) +} + +/// A string that runs to the end of the file +pub(crate) fn unterminated_string(open: TextRange, eof: TextRange) -> ParseDiagnostic { + ParseDiagnostic::new("Unterminated string.", open).with_detail( + eof, + "Reached the end of the file looking for a closing delimiter.", + ) +} + +pub(crate) fn expected_expression(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("expression", range, p) +} + +/// The name on the right of `$`, `@`, `::`, or `:::` is missing +pub(crate) fn expected_rhs_selector( + operator: RSyntaxKind, + operator_range: TextRange, + range: TextRange, +) -> ParseDiagnostic { + ParseDiagnostic::new("Expected a name.", range).with_detail( + operator_range, + format!("`{}` requires a name on the right.", text_of(operator)), + ) +} + +/// The name on the left of `::` or `:::` is not a name +pub(crate) fn expected_lhs_selector( + operator: RSyntaxKind, + operator_range: TextRange, + range: TextRange, +) -> ParseDiagnostic { + ParseDiagnostic::new("Expected a name.", range).with_detail( + operator_range, + format!("`{}` requires a name on the left.", text_of(operator)), + ) +} + +/// A newline between `::` or `:::` and the name it needs +pub(crate) fn namespace_rhs_selector_after_line_break( + operator: RSyntaxKind, + operator_range: TextRange, +) -> ParseDiagnostic { + let operator = text_of(operator); + + ParseDiagnostic::new( + format!("A newline is not allowed between `{operator}` and the name that follows."), + operator_range, + ) +} + +/// The whole `(...)` group of a function definition is missing, as in `function 1` +pub(crate) fn expected_parameters(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("parameter list", range, p) +} + +/// A parameter with no name, as in `function(a,,b)` or `function(1)` +pub(crate) fn expected_parameter_name(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("parameter name", range, p) +} + +/// The condition of an `if` or `while` is missing +pub(crate) fn expected_condition(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("condition", range, p) +} + +/// The variable of a `for` loop is missing or is not a name, as in `for (1 in x) y` +pub(crate) fn expected_for_variable(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("loop variable", range, p) +} + +/// The sequence a `for` loop iterates over is missing +pub(crate) fn expected_for_sequence(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("loop sequence", range, p) +} + +/// The body of a `for`, `while`, or `repeat` loop is missing +pub(crate) fn expected_loop_body(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("loop body", range, p) +} + +/// The body of a function definition is missing, as in `function(x)` +pub(crate) fn expected_function_body(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("function body", range, p) +} + +pub(crate) fn expected_if_body(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("if body", range, p) +} + +pub(crate) fn expected_else_body(p: &RParser, range: TextRange) -> ParseDiagnostic { + expected_node("else body", range, p) +} + +/// Two expressions with nothing between them, as in `1 2` +pub(crate) fn expected_statement_separator( + previous: TextRange, + range: TextRange, +) -> ParseDiagnostic { + ParseDiagnostic::new( + "Expected a newline or a `;` between two expressions.", + range, + ) + .with_detail(previous, "The first expression ends here.") +} + +/// A `;` with no expression before it on the same line, as in `;1` or `1\n;` +pub(crate) fn unexpected_semicolon(range: TextRange) -> ParseDiagnostic { + ParseDiagnostic::new("Unexpected `;`.", range) + .with_hint("At top level, a `;` must directly follow an expression.") +} + +/// Like [biome_parser::diagnostic::expected_node()], but without the duplicative hint +fn expected_node(name: &str, range: TextRange, p: &RParser) -> ParseDiagnostic { + let article = article_for(name); + + let message = if p.source().text().text_len() <= range.start() { + format!("Expected {article} {name} but instead found the end of the file.") + } else { + format!( + "Expected {article} {name} but instead found '{}'.", + truncate(p.text(range)) + ) + }; + + ParseDiagnostic::new(message, range) +} + +fn truncate(text: &str) -> Cow<'_, str> { + const MAX_LEN: usize = 40; + + if text.chars().count() <= MAX_LEN { + Cow::Borrowed(text) + } else { + let mut truncated: String = text.chars().take(MAX_LEN).collect(); + truncated.push_str("..."); + Cow::Owned(truncated) + } +} + +fn article_for(name: &str) -> &'static str { + match name.bytes().next() { + Some(b'a' | b'e' | b'i' | b'o' | b'u') => "an", + _ => "a", + } +} + +fn text_of(operator: RSyntaxKind) -> &'static str { + operator.to_string().unwrap_or("operator") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn truncate_keeps_short_text_untouched() { + assert!(matches!(truncate("abc"), Cow::Borrowed("abc"))); + } + + #[test] + fn truncate_keeps_exactly_max_len_untouched() { + let text = "a".repeat(40); + assert!(matches!(truncate(&text), Cow::Borrowed(borrowed) if borrowed == text)); + } + + #[test] + fn truncate_cuts_and_appends_dots_past_max_len() { + let text = "a".repeat(41); + let expected = format!("{}...", "a".repeat(40)); + assert_eq!(&*truncate(&text), expected); + } + + #[test] + fn truncate_counts_chars_not_bytes() { + let text = "é".repeat(41); + assert_eq!(&*truncate(&text), format!("{}...", "é".repeat(40))); + } +} diff --git a/crates/air_r_parser/src/lexer.rs b/crates/air_r_parser/src/lexer.rs new file mode 100644 index 00000000..a413ef01 --- /dev/null +++ b/crates/air_r_parser/src/lexer.rs @@ -0,0 +1,877 @@ +#[cfg(test)] +mod tests; + +use crate::diagnostics::unexpected_character; +use crate::diagnostics::unterminated_backtick_identifier; +use crate::diagnostics::unterminated_exponent; +use crate::diagnostics::unterminated_hexadecimal; +use crate::diagnostics::unterminated_hexadecimal_binary_exponent; +use crate::diagnostics::unterminated_special; +use crate::diagnostics::unterminated_string; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::*; +use air_r_syntax::T; +use biome_parser::diagnostic::ParseDiagnostic; +use biome_parser::lexer::LexContext; +use biome_parser::lexer::Lexer; +use biome_parser::lexer::LexerCheckpoint; +use biome_parser::lexer::LexerWithCheckpoint; +use biome_parser::lexer::ReLexer; +use biome_parser::lexer::TokenFlags; +use biome_rowan::SyntaxKind; +use biome_rowan::TextRange; +use biome_rowan::TextSize; +use unicode_ident::is_xid_continue; +use unicode_ident::is_xid_start; + +/// Lex context for R +/// +/// There aren't any special contexts for R, so we just have a single `Regular` +/// context that everything is lexed in. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub(crate) enum RLexContext { + #[default] + Regular, +} + +impl LexContext for RLexContext { + fn is_regular(&self) -> bool { + // Because we only have `Regular`! + true + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum RStringDelimiter { + Single, + Double, + /// `r"---[...]---"`, closed by the matching bracket, the same number of + /// hyphens, and the same quote + Raw { + quote: u8, + hyphens: u8, + bracket: RawStringBracket, + }, +} + +impl RStringDelimiter { + /// The raw string delimiter opening at the start of `text`, or `None` when + /// none does + /// + /// Reads no further than the delimiter itself + fn try_from_raw_string_open(text: &str) -> Option { + let bytes = text.as_bytes(); + + if !matches!(bytes.first()?, b'r' | b'R') { + return None; + } + + let quote = match bytes.get(1)? { + quote @ (b'\'' | b'"') => *quote, + _ => return None, + }; + + let hyphens = bytes[2..].iter().take_while(|byte| **byte == b'-').count(); + + // Bail on pathological 256 hyphens + let hyphens = u8::try_from(hyphens).ok()?; + let bracket = RawStringBracket::from_open(*bytes.get(2 + usize::from(hyphens))?)?; + + Some(Self::Raw { + quote, + hyphens, + bracket, + }) + } + + fn kind(self) -> RSyntaxKind { + match self { + Self::Single => R_SINGLE_QUOTED_STRING_LITERAL, + Self::Double => R_DOUBLE_QUOTED_STRING_LITERAL, + Self::Raw { .. } => R_RAW_STRING_LITERAL, + } + } + + /// The string quote character + fn quote(self) -> u8 { + match self { + Self::Single => b'\'', + Self::Double => b'"', + Self::Raw { quote, .. } => quote, + } + } + + /// The byte length of the opening delimiter + fn open_length(self) -> usize { + match self { + Self::Single | Self::Double => 1, + // `r`, the quote, the hyphens, and the bracket + Self::Raw { hyphens, .. } => 3 + usize::from(hyphens), + } + } + + /// The byte length of the closing delimiter + fn close_length(self) -> usize { + match self { + Self::Single | Self::Double => 1, + // The bracket, the hyphens, and the quote + Self::Raw { hyphens, .. } => 2 + usize::from(hyphens), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum RawStringBracket { + Paren, + Bracket, + Brace, + Pipe, +} + +impl RawStringBracket { + fn from_open(byte: u8) -> Option { + match byte { + b'(' => Some(Self::Paren), + b'[' => Some(Self::Bracket), + b'{' => Some(Self::Brace), + b'|' => Some(Self::Pipe), + _ => None, + } + } + + fn close(self) -> u8 { + match self { + Self::Paren => b')', + Self::Bracket => b']', + Self::Brace => b'}', + Self::Pipe => b'|', + } + } +} + +/// The re-lexing requests the parser can make +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum RReLexContext { + /// Re-lex `]` as `]]` when the very next byte is another `]`. A no-op on any + /// other current token. + JoinBrackets, +} + +impl RLexer<'_> { + fn re_lex_join_brackets(&mut self) -> RSyntaxKind { + match self.current_kind { + T![']'] if self.peek_byte() == Some(b']') => self.eat_bytes(2, T!["]]"]), + kind => kind, + } + } +} + +pub(crate) struct RLexer<'src> { + source: &'src str, + + /// Byte offset of the next token + position: usize, + + /// Byte offset of the current token. Its range is `current_start..position`. + current_start: TextSize, + + current_kind: RSyntaxKind, + + current_flags: TokenFlags, + + /// Sticky across `next_token` calls: set by `consume_newline_or_whitespaces` + /// and cleared only once a non-trivia token has been stamped with it. This + /// is what lets the lexer report a preceding line break even though + /// `NEWLINE` is a token in its own right, and it is exactly + /// what `biome_js_parser` does. + after_line_break: bool, + + /// Byte length of the BOM, if the source starts with one + /// + /// Nothing in R reads this. It exists because [`LexerCheckpoint`] carries + /// it, and setting it from `consume_potential_bom` costs one line where a + /// hardcoded `0` would be a lie the next reader has to re-derive. + unicode_bom_length: usize, + + diagnostics: Vec, +} + +impl<'src> Lexer<'src> for RLexer<'src> { + const NEWLINE: Self::Kind = NEWLINE; + const WHITESPACE: Self::Kind = WHITESPACE; + + type Kind = RSyntaxKind; + type LexContext = RLexContext; + type ReLexContext = RReLexContext; + + fn source(&self) -> &'src str { + self.source + } + + fn current(&self) -> Self::Kind { + self.current_kind + } + + fn current_start(&self) -> TextSize { + self.current_start + } + + /// There's only one "Regular" context for R + fn next_token(&mut self, _context: Self::LexContext) -> Self::Kind { + self.current_start = self.text_position(); + self.current_flags = TokenFlags::empty(); + + let kind = if self.is_eof() { EOF } else { self.lex_token() }; + + // Stamped after lexing so that the lex functions are free to set other + // flags first + self.current_flags + .set(TokenFlags::PRECEDING_LINE_BREAK, self.after_line_break); + self.current_kind = kind; + + // The bit survives across trivia, so `1\n # c\n + 2` reports the break + // on `+` rather than on the whitespace that happens to precede it + if !kind.is_trivia() { + self.after_line_break = false; + } + + debug_assert!(self.current_start <= self.text_position()); + kind + } + + fn has_preceding_line_break(&self) -> bool { + self.current_flags.has_preceding_line_break() + } + + /// R has no token-level unicode escapes + fn has_unicode_escape(&self) -> bool { + false + } + + fn rewind(&mut self, checkpoint: LexerCheckpoint) { + let LexerCheckpoint { + position, + current_start, + current_kind, + current_flags, + after_line_break, + unicode_bom_length, + diagnostics_pos, + } = checkpoint; + + self.position = u32::from(position) as usize; + self.current_start = current_start; + self.current_kind = current_kind; + self.current_flags = current_flags; + self.after_line_break = after_line_break; + self.unicode_bom_length = unicode_bom_length; + self.diagnostics.truncate(diagnostics_pos as usize); + } + + fn finish(self) -> Vec { + self.diagnostics + } + + fn current_flags(&self) -> TokenFlags { + self.current_flags + } + + fn position(&self) -> usize { + self.position + } + + fn push_diagnostic(&mut self, diagnostic: ParseDiagnostic) { + self.diagnostics.push(diagnostic); + } + + fn advance(&mut self, n: usize) { + self.position += n; + } + + fn advance_char_unchecked(&mut self) { + let char = self.current_char_unchecked(); + self.position += char.len_utf8(); + } + + /// Overridden to update `after_line_break`. The default cannot since this is + /// a custom field. `biome_js_parser` overrides it for the same reason. + /// + /// Inheriting the trait's `consume_newline` also gets `\r\n` treated as a + /// single newline for free. + fn consume_newline_or_whitespaces(&mut self) -> Self::Kind { + if self.consume_newline() { + self.after_line_break = true; + NEWLINE + } else { + self.consume_whitespaces(); + WHITESPACE + } + } + + /// R's whitespace is ASCII: space, tab, vertical tab, and form feed + /// + /// The trait's body is written for CSS and diagnoses vertical tab and form + /// feed, which are ordinary whitespace here. + fn consume_whitespaces(&mut self) { + while let Some(b'\t' | b' ' | b'\x0B' | b'\x0C') = self.current_byte() { + self.advance(1); + } + } +} + +impl<'src> ReLexer<'src> for RLexer<'src> { + fn re_lex(&mut self, context: Self::ReLexContext) -> Self::Kind { + let old_position = self.position; + self.position = u32::from(self.current_start) as usize; + + let new_kind = match context { + RReLexContext::JoinBrackets => self.re_lex_join_brackets(), + }; + + if self.current_kind == new_kind { + // Nothing was re-lexed. Return the existing token untouched. + self.position = old_position; + } else { + self.current_kind = new_kind; + } + + new_kind + } +} + +impl<'src> LexerWithCheckpoint<'src> for RLexer<'src> { + fn checkpoint(&self) -> LexerCheckpoint { + LexerCheckpoint { + position: self.text_position(), + current_start: self.current_start, + current_kind: self.current_kind, + current_flags: self.current_flags, + after_line_break: self.after_line_break, + unicode_bom_length: self.unicode_bom_length, + diagnostics_pos: self.diagnostics.len() as u32, + } + } +} + +impl<'src> RLexer<'src> { + pub(crate) fn from_str(source: &'src str) -> Self { + Self { + source, + position: 0, + current_start: TextSize::from(0), + current_kind: TOMBSTONE, + current_flags: TokenFlags::empty(), + after_line_break: false, + unicode_bom_length: 0, + diagnostics: Vec::new(), + } + } + + fn lex_token(&mut self) -> RSyntaxKind { + debug_assert!(!self.is_eof()); + + match self.current_byte_unchecked() { + b'\n' | b'\r' | b'\t' | b' ' | b'\x0B' | b'\x0C' => { + self.consume_newline_or_whitespaces() + } + + b'#' => self.consume_comment(), + + b'"' => self.consume_string(RStringDelimiter::Double), + b'\'' => self.consume_string(RStringDelimiter::Single), + b'`' => self.consume_backtick_identifier(), + + // Ahead of the digit arm, which would otherwise take the `0` + b'0' if matches!(self.peek_byte(), Some(b'x' | b'X')) => self.consume_hexadecimal(), + b'0'..=b'9' => self.consume_number(), + + // `.` is overloaded four ways: `...`, `..1`, `.5`, and `.foo` + b'.' => self.consume_dot(), + + b'%' => self.consume_special(), + + // Raw strings share this arm with every other name + // rather than taking one of their own, which would split the letter + // range and cost the whole match its jump table for `s` onwards. + b'_' | b'a'..=b'z' | b'A'..=b'Z' => { + match RStringDelimiter::try_from_raw_string_open(&self.source[self.position..]) { + Some(delimiter) => self.consume_string(delimiter), + None => self.consume_identifier_or_keyword(), + } + } + + b';' => self.eat_byte(T![;]), + b',' => self.eat_byte(T![,]), + b'{' => self.eat_byte(T!['{']), + b'}' => self.eat_byte(T!['}']), + b'(' => self.eat_byte(T!['(']), + b')' => self.eat_byte(T![')']), + b'?' => self.eat_byte(T![?]), + b'~' => self.eat_byte(T![~]), + b'+' => self.eat_byte(T![+]), + b'/' => self.eat_byte(T![/]), + b'^' => self.eat_byte(T![^]), + b'$' => self.eat_byte(T![$]), + b'@' => self.eat_byte(T![@]), + b'\\' => self.eat_byte(T!["\\"]), + + // Lexer emits `]` but never `]]`. Only the parser can decide if we + // are in a [`RReLexContext::JoinBrackets`] context or not, at which + // point re-lexing will join contiguous brackets. + b']' => self.eat_byte(T![']']), + + // `[[` is greedy in R's own `gram.y` and in tree-sitter-r's scanner. + // Two adjacent opening brackets are always a subset2. + b'[' => match self.peek_byte() { + Some(b'[') => self.eat_bytes(2, T!["[["]), + _ => self.eat_byte(T!['[']), + }, + b'<' => match (self.peek_byte(), self.byte_at(2)) { + (Some(b'<'), Some(b'-')) => self.eat_bytes(3, T![<<-]), + (Some(b'-'), _) => self.eat_bytes(2, T![<-]), + (Some(b'='), _) => self.eat_bytes(2, T![<=]), + _ => self.eat_byte(T![<]), + }, + b'-' => match (self.peek_byte(), self.byte_at(2)) { + (Some(b'>'), Some(b'>')) => self.eat_bytes(3, T![->>]), + (Some(b'>'), _) => self.eat_bytes(2, T![->]), + _ => self.eat_byte(T![-]), + }, + b':' => match (self.peek_byte(), self.byte_at(2)) { + (Some(b':'), Some(b':')) => self.eat_bytes(3, T![:::]), + (Some(b':'), _) => self.eat_bytes(2, T![::]), + (Some(b'='), _) => self.eat_bytes(2, T![:=]), + _ => self.eat_byte(T![:]), + }, + b'>' => match self.peek_byte() { + Some(b'=') => self.eat_bytes(2, T![>=]), + _ => self.eat_byte(T![>]), + }, + b'=' => match self.peek_byte() { + Some(b'=') => self.eat_bytes(2, T![==]), + _ => self.eat_byte(T![=]), + }, + b'!' => match self.peek_byte() { + Some(b'=') => self.eat_bytes(2, T![!=]), + _ => self.eat_byte(T![!]), + }, + b'&' => match self.peek_byte() { + Some(b'&') => self.eat_bytes(2, T![&&]), + _ => self.eat_byte(T![&]), + }, + b'|' => match self.peek_byte() { + Some(b'|') => self.eat_bytes(2, T![||]), + Some(b'>') => self.eat_bytes(2, T![|>]), + _ => self.eat_byte(T![|]), + }, + b'*' => match self.peek_byte() { + Some(b'*') => self.eat_bytes(2, T![**]), + _ => self.eat_byte(T![*]), + }, + + byte if byte >= 128 => self.consume_unicode(), + + _ => self.consume_unexpected(), + } + } + + fn current_byte_unchecked(&self) -> u8 { + debug_assert!(!self.is_eof()); + self.source.as_bytes()[self.position] + } + + fn eat_byte(&mut self, kind: RSyntaxKind) -> RSyntaxKind { + self.advance(1); + kind + } + + fn eat_bytes(&mut self, n: usize, kind: RSyntaxKind) -> RSyntaxKind { + self.advance(n); + kind + } + + /// `#` to the end of the line, exclusive of the line break itself + fn consume_comment(&mut self) -> RSyntaxKind { + self.assert_byte(b'#'); + self.advance(1); + + // We're just scanning for the next `\n` or `\r`, so we don't need to worry about multi + // byte UTF-8 characters here. This is faster than calling `advance_byte_or_char()` in a loop. + let rest = &self.source.as_bytes()[self.position..]; + let len = rest + .iter() + .position(|byte| matches!(byte, b'\n' | b'\r')) + .unwrap_or(rest.len()); + self.advance(len); + + COMMENT + } + + // ----------------------------------------------------------------------- + // Identifiers and keywords + + /// Consumes an identifier mimicking `[\p{XID_Start}_][\p{XID_Continue}.]*`. + /// If the consumed identifier maps to a keyword, that is returned instead. + fn consume_identifier_or_keyword(&mut self) -> RSyntaxKind { + let start = self.position; + // `XID_Start` or `_` + self.advance_char_unchecked(); + self.consume_xid_continue_or_dot(); + + Self::keyword_kind(&self.source[start..self.position]).unwrap_or(IDENT) + } + + /// `[\p{XID_Continue}.]*` + fn consume_xid_continue_or_dot(&mut self) { + loop { + match self.current_byte() { + Some(b'.' | b'_' | b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z') => self.advance(1), + Some(byte) if byte >= 128 => { + if !is_xid_continue(self.current_char_unchecked()) { + break; + } + self.advance_char_unchecked(); + } + _ => break, + } + } + } + + /// R's keywords and constants, spelled as they appear in source + fn keyword_kind(text: &str) -> Option { + Some(match text { + "function" => T![function], + "for" => T![for], + "in" => T![in], + "while" => T![while], + "repeat" => T![repeat], + "if" => T![if], + "else" => T![else], + "next" => T![next], + "break" => T![break], + "TRUE" => T![true], + "FALSE" => T![false], + "NULL" => T![null], + "Inf" => T![inf], + "NaN" => T![nan], + "NA" => T![na_logical], + "NA_integer_" => T![na_integer], + "NA_real_" => T![na_double], + "NA_complex_" => T![na_complex], + "NA_character_" => T![na_character], + _ => return None, + }) + } + + fn consume_backtick_identifier(&mut self) -> RSyntaxKind { + self.assert_byte(b'`'); + let open = TextRange::at(self.text_position(), TextSize::from(1)); + self.advance(1); + + while let Some(byte) = self.current_byte() { + match byte { + b'`' => { + self.advance(1); + return IDENT; + } + b'\\' => { + self.advance(1); + if let Some(escaped) = self.current_byte() { + self.advance_byte_or_char(escaped); + } + } + byte => self.advance_byte_or_char(byte), + } + } + + let eof = TextRange::empty(self.text_position()); + self.push_diagnostic(unterminated_backtick_identifier(open, eof)); + ERROR_TOKEN + } + + /// The `byte >= 128` arm, which is also where the BOM is resolved + fn consume_unicode(&mut self) -> RSyntaxKind { + // A BOM is only a BOM at offset 0. Elsewhere it falls through as unexpected. + if self.position == 0 + && let Some((bom, length)) = self.consume_potential_bom(UNICODE_BOM) + { + self.unicode_bom_length = length; + return bom; + } + + if is_xid_start(self.current_char_unchecked()) { + self.consume_identifier_or_keyword() + } else { + self.consume_unexpected() + } + } + + // ----------------------------------------------------------------------- + // Dots + + /// `...`, `..1`, `.5`, and `.foo` all start with a `.` + /// + /// R decides between a number and a name on the single character after the + /// `.` (`typeofnext()` in `src/main/gram.y`), then consumes one whole name + /// and classifies its text. `...` and `..i` are not tokens of their own + /// there — `"..."` is a row in R's keyword table, and `..1` is an ordinary + /// symbol. + fn consume_dot(&mut self) -> RSyntaxKind { + self.assert_byte(b'.'); + + // A digit after the `.` always makes a number, never a name, otherwise + // `.1i` and `.1L` lex as identifiers (r-lib/tree-sitter-r#190). + if matches!(self.peek_byte(), Some(b'0'..=b'9')) { + return self.consume_number(); + } + + let start = self.position; + self.advance(1); + self.consume_xid_continue_or_dot(); + + Self::dot_kind(&self.source[start..self.position]) + } + + /// The two names the parser has to tell apart from any other identifier + fn dot_kind(text: &str) -> RSyntaxKind { + match text.as_bytes() { + b"..." => T![...], + [b'.', b'.', digits @ ..] + if !digits.is_empty() && digits.iter().all(u8::is_ascii_digit) => + { + DOTDOTI + } + _ => IDENT, + } + } + + // ----------------------------------------------------------------------- + // Numbers + + /// `(?:(?:\d+(?:\.\d*)?)|(?:\.\d+))(?:[eE][+-]?\d+)?` + fn consume_number(&mut self) -> RSyntaxKind { + let start = self.text_position(); + self.consume_digits(); + + if self.current_byte() == Some(b'.') { + self.advance(1); + self.consume_digits(); + } + + if matches!(self.current_byte(), Some(b'e' | b'E')) { + self.advance(1); + + if !self.consume_exponent() { + let range = TextRange::new(start, self.text_position()); + self.push_diagnostic(unterminated_exponent(range)); + } + } + + self.consume_suffix() + } + + /// `0[xX](([0-9a-fA-F]+(\.[0-9a-fA-F]*)?)|(\.[0-9a-fA-F]*))([pP][+-]?[0-9]+)?` + fn consume_hexadecimal(&mut self) -> RSyntaxKind { + let start = self.text_position(); + + // The `0x` + self.advance(2); + + let has_body = self + .current_byte() + .is_some_and(|byte| byte == b'.' || byte.is_ascii_hexdigit()); + + if !has_body { + let range = TextRange::new(start, self.text_position()); + self.push_diagnostic(unterminated_hexadecimal(range)); + return self.consume_suffix(); + } + + self.consume_hex_digits(); + + if self.current_byte() == Some(b'.') { + self.advance(1); + self.consume_hex_digits(); + } + + if matches!(self.current_byte(), Some(b'p' | b'P')) { + self.advance(1); + + if !self.consume_exponent() { + let range = TextRange::new(start, self.text_position()); + self.push_diagnostic(unterminated_hexadecimal_binary_exponent(range)); + } + } + + self.consume_suffix() + } + + /// The `L` and `i` that turn the literal before them into an integer or a + /// complex + fn consume_suffix(&mut self) -> RSyntaxKind { + match self.current_byte() { + Some(b'L') => self.eat_byte(R_INTEGER_LITERAL), + Some(b'i') => self.eat_byte(R_COMPLEX_LITERAL), + _ => R_DOUBLE_LITERAL, + } + } + + /// An optional sign and the exponent's digits, reporting whether the digits + /// were there + fn consume_exponent(&mut self) -> bool { + if matches!(self.current_byte(), Some(b'+' | b'-')) { + self.advance(1); + } + + self.consume_digits() + } + + /// Whether any digit was consumed + fn consume_digits(&mut self) -> bool { + let start = self.position; + while matches!(self.current_byte(), Some(byte) if byte.is_ascii_digit()) { + self.advance(1); + } + self.position != start + } + + /// Whether any digit was consumed + fn consume_hex_digits(&mut self) -> bool { + let start = self.position; + while matches!(self.current_byte(), Some(byte) if byte.is_ascii_hexdigit()) { + self.advance(1); + } + self.position != start + } + + // ----------------------------------------------------------------------- + // Special operators + + /// `/%[^%\n]*%/`, i.e. `%%`, `%/%`, `%in%`, `%o%`, and friends + /// + /// Unlike strings and backticked names, a `%...%` may not span a newline. + /// The partial `%foo` is kept as a `SPECIAL` carrying the diagnostic. + fn consume_special(&mut self) -> RSyntaxKind { + self.assert_byte(b'%'); + let open = TextRange::at(self.text_position(), TextSize::from(1)); + + // The `%` + self.advance(1); + + while let Some(byte) = self.current_byte() { + match byte { + b'%' => { + self.advance(1); + return SPECIAL; + } + b'\n' | b'\r' => break, + byte => self.advance_byte_or_char(byte), + } + } + + self.push_diagnostic(unterminated_special(open)); + SPECIAL + } + + // ----------------------------------------------------------------------- + // Strings + + fn consume_string(&mut self, delimiter: RStringDelimiter) -> RSyntaxKind { + let open = TextRange::at( + self.text_position(), + TextSize::from(delimiter.open_length() as u32), + ); + self.advance(delimiter.open_length()); + + let terminated = match delimiter { + RStringDelimiter::Single | RStringDelimiter::Double => { + self.consume_quoted_string_content(delimiter.quote()) + } + RStringDelimiter::Raw { + quote, + hyphens, + bracket, + } => self.consume_raw_string_content(quote, hyphens, bracket), + }; + + if !terminated { + let eof = TextRange::empty(self.text_position()); + self.push_diagnostic(unterminated_string(open, eof)); + return ERROR_TOKEN; + } + + self.advance(delimiter.close_length()); + delimiter.kind() + } + + /// Everything up to the closing quote + fn consume_quoted_string_content(&mut self, quote: u8) -> bool { + while let Some(byte) = self.current_byte() { + match byte { + b'\\' => { + self.advance(1); + if let Some(escaped) = self.current_byte() { + self.advance_byte_or_char(escaped); + } + } + byte if byte == quote => return true, + byte => self.advance_byte_or_char(byte), + } + } + + // Unterminated string + false + } + + /// Everything up to the closing sequence + /// + /// The subtle part is the closing bracket that turns out not to start one. + /// It belongs to the body, and the scan carries on from the byte after it, + /// or `r"(())"` and `r"-())-"` never find their close + /// (r-lib/tree-sitter-r#162). + fn consume_raw_string_content( + &mut self, + quote: u8, + hyphens: u8, + bracket: RawStringBracket, + ) -> bool { + while let Some(byte) = self.current_byte() { + if self.at_raw_string_close(quote, hyphens, bracket) { + return true; + } + self.advance_byte_or_char(byte); + } + + // Unterminated string + false + } + + /// Whether a whole closing sequence — the bracket, the hyphens, and the + /// quote — starts at the current position + fn at_raw_string_close(&self, quote: u8, hyphens: u8, bracket: RawStringBracket) -> bool { + if self.current_byte() != Some(bracket.close()) { + return false; + } + + let hyphens = usize::from(hyphens); + (1..=hyphens).all(|offset| self.byte_at(offset) == Some(b'-')) + && self.byte_at(hyphens + 1) == Some(quote) + } + + // ----------------------------------------------------------------------- + // Errors + + /// A character that starts no token + /// + /// Exactly one character is consumed and passed on as `ERROR_TOKEN`, which + /// is what `biome_js_parser` also does. + fn consume_unexpected(&mut self) -> RSyntaxKind { + let start = self.text_position(); + let character = self.current_char_unchecked(); + self.advance(character.len_utf8()); + + let range = TextRange::new(start, self.text_position()); + self.push_diagnostic(unexpected_character(range, character)); + ERROR_TOKEN + } +} diff --git a/crates/air_r_parser/src/lexer/tests.rs b/crates/air_r_parser/src/lexer/tests.rs new file mode 100644 index 00000000..b04aeccb --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests.rs @@ -0,0 +1,176 @@ +//! Lexer test infrastructure + +mod backticks; +mod bom; +mod brackets; +mod checkpoint; +mod comments; +mod coverage; +mod dots; +mod error_token; +mod identifiers; +mod keywords; +mod line_breaks; +mod numbers; +mod puncts; +mod raw_strings; +mod specials; +mod strings; +mod whitespace; + +use super::*; +use biome_diagnostics::PrintDescription; + +#[derive(Debug)] +pub(super) struct LexedToken { + kind: RSyntaxKind, + range: TextRange, + preceding_line_break: bool, +} + +/// Lex `source` to completion +pub(super) fn lex(source: &str) -> (Vec, Vec) { + let mut lexer = RLexer::from_str(source); + let mut tokens = Vec::new(); + + loop { + let kind = lexer.next_token(RLexContext::Regular); + if kind == EOF { + break; + } + + tokens.push(LexedToken { + kind, + range: lexer.current_range(), + preceding_line_break: lexer.has_preceding_line_break(), + }); + } + + (tokens, lexer.finish()) +} + +/// Assert the exact token stream, and that the lexer emitted no diagnostics +macro_rules! assert_lex { + ($source:expr, $($kind:ident : $length:expr),* $(,)?) => { + $crate::lexer::tests::check_lex($source, &[$(($kind, $length)),*], &[]) + }; +} +// `macro_rules!` scoping is textual, so the three macros reach the submodules +// declared above them by path rather than by falling into scope +pub(super) use assert_lex; + +/// [`assert_lex!`] plus the diagnostics expected, in order +macro_rules! assert_lex_err { + ($source:expr, $($kind:ident : $length:expr),* $(,)? => $($message:literal),+ $(,)?) => { + $crate::lexer::tests::check_lex($source, &[$(($kind, $length)),*], &[$($message),+]) + }; +} +pub(super) use assert_lex_err; + +/// Assert `PRECEDING_LINE_BREAK` on each non-trivia token, in order +macro_rules! assert_line_breaks { + ($source:expr, $($kind:ident : $preceded:literal),* $(,)?) => { + $crate::lexer::tests::check_line_breaks($source, &[$(($kind, $preceded)),*]) + }; +} +pub(super) use assert_line_breaks; + +#[track_caller] +pub(super) fn check_lex(source: &str, expected: &[(RSyntaxKind, u32)], messages: &[&str]) { + let (tokens, diagnostics) = lex(source); + let render = render(source, &tokens); + + assert_eq!( + tokens.len(), + expected.len(), + "expected {} tokens, lexed {}:\n{render}", + expected.len(), + tokens.len(), + ); + + // The ranges must cover the whole source + let mut offset = TextSize::from(0); + for (index, (token, (want_kind, want_length))) in tokens.iter().zip(expected).enumerate() { + assert_eq!(token.kind, *want_kind, "token {index}:\n{render}"); + assert_eq!( + token.range.len(), + TextSize::from(*want_length), + "token {index} has the wrong length:\n{render}", + ); + assert_eq!( + token.range.start(), + offset, + "token {index} leaves a gap:\n{render}", + ); + offset = token.range.end(); + } + assert_eq!( + usize::from(offset), + source.len(), + "the tokens stop short of the end of the source:\n{render}", + ); + + let actual: Vec = diagnostics + .iter() + .map(|diagnostic| PrintDescription(diagnostic).to_string()) + .collect(); + + assert_eq!( + actual.len(), + messages.len(), + "expected {} diagnostics, got:\n{actual:#?}", + messages.len(), + ); + for (actual, expected) in actual.iter().zip(messages) { + assert!( + actual.contains(expected), + "expected {expected:?} in {actual:?}" + ); + } +} + +#[track_caller] +pub(super) fn check_line_breaks(source: &str, expected: &[(RSyntaxKind, bool)]) { + let (tokens, _) = lex(source); + let render = render(source, &tokens); + + let actual: Vec<_> = tokens + .iter() + .filter(|token| !token.kind.is_trivia()) + .collect(); + + assert_eq!( + actual.len(), + expected.len(), + "expected {} non-trivia tokens, lexed {}:\n{render}", + expected.len(), + actual.len(), + ); + for (index, (token, (want_kind, want_break))) in actual.iter().zip(expected).enumerate() { + assert_eq!(token.kind, *want_kind, "token {index}:\n{render}"); + assert_eq!( + token.preceding_line_break, *want_break, + "token {index} reports the wrong preceding line break:\n{render}", + ); + } +} + +/// The whole stream, for any failure. A message naming only the offending token +/// sends you back to re-run with `--no-capture` and print the rest yourself. +fn render(source: &str, tokens: &[LexedToken]) -> String { + tokens + .iter() + .map(|token| { + let break_marker = if token.preceding_line_break { + " nl" + } else { + "" + }; + format!( + " {:?}@{:?} {:?}{break_marker}", + token.kind, token.range, &source[token.range] + ) + }) + .collect::>() + .join("\n") +} diff --git a/crates/air_r_parser/src/lexer/tests/backticks.rs b/crates/air_r_parser/src/lexer/tests/backticks.rs new file mode 100644 index 00000000..1969c942 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/backticks.rs @@ -0,0 +1,49 @@ +use super::*; + +#[test] +fn one_token_spanning_the_delimiters() { + assert_lex!("`a b`", IDENT: 5); + assert_lex!("`a b` <- 1", IDENT: 5, WHITESPACE: 1, ASSIGN: 2, WHITESPACE: 1, R_DOUBLE_LITERAL: 1); +} + +#[test] +fn empty_name() { + assert_lex!("``", IDENT: 2); +} + +/// An escaped backtick does not close the name +#[test] +fn escapes_apply() { + assert_lex!("`a\\`b`", IDENT: 6); + assert_lex!("`a\\\\`", IDENT: 5); +} + +#[test] +fn names_may_span_newlines() { + assert_lex!( + "`my\nvar` <- 1", + IDENT: 8, + WHITESPACE: 1, + ASSIGN: 2, + WHITESPACE: 1, + R_DOUBLE_LITERAL: 1, + ); +} + +/// There is no line-based early stop available, so an unterminated name really +/// does run to the end of the file, exactly as in R and in tree-sitter-r. What +/// keeps the error useful is that it points at the opening delimiter rather than +/// highlighting the rest of the file. +#[test] +fn unterminated_runs_to_the_end_of_the_file() { + assert_lex_err!( + "`a\nb <- 1", + ERROR_TOKEN: 9 + => "Unterminated backtick-quoted name" + ); +} + +#[test] +fn a_trailing_escape_does_not_run_past_the_end() { + assert_lex_err!("`a\\", ERROR_TOKEN: 3 => "Unterminated backtick-quoted name"); +} diff --git a/crates/air_r_parser/src/lexer/tests/bom.rs b/crates/air_r_parser/src/lexer/tests/bom.rs new file mode 100644 index 00000000..90c42b5f --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/bom.rs @@ -0,0 +1,30 @@ +use super::*; + +#[test] +fn bare_bom() { + assert_lex!("\u{FEFF}", UNICODE_BOM: 3); +} + +#[test] +fn bom_before_a_token() { + assert_lex!("\u{FEFF}1", UNICODE_BOM: 3, R_DOUBLE_LITERAL: 1); +} + +#[test] +fn a_bom_is_only_a_bom_at_offset_zero() { + // U+FEFF is category Cf, so it is neither XID_Start nor XID_Continue and + // cannot join the identifier either + assert_lex_err!( + "a\u{FEFF}", + IDENT: 1, ERROR_TOKEN: 3 + => "Unexpected character" + ); +} + +// `consume_potential_bom` matches on `source().get(0..3)`, so a source shorter +// than three bytes has to fall through rather than panic or mis-slice +#[test] +fn source_shorter_than_a_bom() { + assert_lex!("a", IDENT: 1); + assert_lex!("é", IDENT: 2); +} diff --git a/crates/air_r_parser/src/lexer/tests/brackets.rs b/crates/air_r_parser/src/lexer/tests/brackets.rs new file mode 100644 index 00000000..631924a7 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/brackets.rs @@ -0,0 +1,112 @@ +//! `[[` is lexed greedily, `]]` is not + +use super::*; + +#[test] +fn adjacent_open_brackets_are_one_token() { + assert_lex!("[[", L_BRACK2: 2); +} + +#[test] +fn adjacent_close_brackets_are_not() { + assert_lex!("]]", R_BRACK: 1, R_BRACK: 1); +} + +#[test] +fn separated_open_brackets_are_not() { + assert_lex!( + "x[ [1] ]", + IDENT: 1, + L_BRACK: 1, + WHITESPACE: 1, + L_BRACK: 1, + R_DOUBLE_LITERAL: 1, + R_BRACK: 1, + WHITESPACE: 1, + R_BRACK: 1, + ); +} + +#[test] +fn a_third_open_bracket_stands_alone() { + assert_lex!("[[[", L_BRACK2: 2, L_BRACK: 1); +} + +#[test] +fn join_takes_both_brackets() { + let mut lexer = RLexer::from_str("]]a"); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_BRACK); + assert_eq!(lexer.re_lex(RReLexContext::JoinBrackets), R_BRACK2); + assert_eq!(lexer.current_range(), TextRange::new(0.into(), 2.into())); + + assert_eq!(lexer.next_token(RLexContext::Regular), IDENT); + assert_eq!(lexer.current_range(), TextRange::new(2.into(), 3.into())); +} + +/// `x[[a[[b]]]]` — the outer subset2 joins the pair the inner one left behind +#[test] +fn a_joined_pair_leaves_the_rest_of_the_run() { + let mut lexer = RLexer::from_str("]]]]"); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_BRACK); + assert_eq!(lexer.re_lex(RReLexContext::JoinBrackets), R_BRACK2); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_BRACK); + assert_eq!(lexer.re_lex(RReLexContext::JoinBrackets), R_BRACK2); + assert_eq!(lexer.current_range(), TextRange::new(2.into(), 4.into())); +} + +#[test] +fn join_is_idempotent() { + let mut lexer = RLexer::from_str("]]"); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_BRACK); + assert_eq!(lexer.re_lex(RReLexContext::JoinBrackets), R_BRACK2); + assert_eq!(lexer.re_lex(RReLexContext::JoinBrackets), R_BRACK2); + assert_eq!(lexer.current_range(), TextRange::new(0.into(), 2.into())); +} + +/// `x[[1] ]` and issue #177's `x[[1]\n]` both land here: the brackets must be +/// adjacent, and a no-op join must leave the trivia between them intact +#[test] +fn join_needs_the_brackets_adjacent() { + for source in ["] ]", "]\n]"] { + let mut lexer = RLexer::from_str(source); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_BRACK); + assert_eq!(lexer.re_lex(RReLexContext::JoinBrackets), R_BRACK); + assert_eq!(lexer.current_range(), TextRange::new(0.into(), 1.into())); + + assert!(matches!( + lexer.next_token(RLexContext::Regular), + WHITESPACE | NEWLINE + )); + assert_eq!(lexer.next_token(RLexContext::Regular), R_BRACK); + assert_eq!(lexer.next_token(RLexContext::Regular), EOF); + } +} + +#[test] +fn join_is_a_no_op_at_the_end_of_the_source() { + let mut lexer = RLexer::from_str("]"); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_BRACK); + assert_eq!(lexer.re_lex(RReLexContext::JoinBrackets), R_BRACK); + assert_eq!(lexer.current_range(), TextRange::new(0.into(), 1.into())); +} + +/// A join rewinds to the start of the current token, so it must not lose the +/// line break flag +#[test] +fn join_preserves_the_preceding_line_break() { + let mut lexer = RLexer::from_str("1\n]]"); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_DOUBLE_LITERAL); + assert_eq!(lexer.next_token(RLexContext::Regular), NEWLINE); + assert_eq!(lexer.next_token(RLexContext::Regular), R_BRACK); + assert!(lexer.has_preceding_line_break()); + + assert_eq!(lexer.re_lex(RReLexContext::JoinBrackets), R_BRACK2); + assert!(lexer.has_preceding_line_break()); +} diff --git a/crates/air_r_parser/src/lexer/tests/checkpoint.rs b/crates/air_r_parser/src/lexer/tests/checkpoint.rs new file mode 100644 index 00000000..476aeb9f --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/checkpoint.rs @@ -0,0 +1,94 @@ +use super::*; + +#[test] +fn rewind_restores_the_current_token() { + let mut lexer = RLexer::from_str("a + b"); + + assert_eq!(lexer.next_token(RLexContext::Regular), IDENT); + let checkpoint = lexer.checkpoint(); + + assert_eq!(lexer.next_token(RLexContext::Regular), WHITESPACE); + assert_eq!(lexer.next_token(RLexContext::Regular), PLUS); + + lexer.rewind(checkpoint); + assert_eq!(lexer.current(), IDENT); + assert_eq!(lexer.current_range(), TextRange::new(0.into(), 1.into())); + assert_eq!(lexer.next_token(RLexContext::Regular), WHITESPACE); +} + +#[test] +fn rewind_restores_the_sticky_line_break() { + let mut lexer = RLexer::from_str("1\n2"); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_DOUBLE_LITERAL); + assert_eq!(lexer.next_token(RLexContext::Regular), NEWLINE); + assert!(lexer.after_line_break); + + // Checkpointed with the break still pending, and consumed straight after + let checkpoint = lexer.checkpoint(); + assert_eq!(lexer.next_token(RLexContext::Regular), R_DOUBLE_LITERAL); + assert!(lexer.has_preceding_line_break()); + assert!(!lexer.after_line_break); + + lexer.rewind(checkpoint); + assert!(lexer.after_line_break); + assert_eq!(lexer.next_token(RLexContext::Regular), R_DOUBLE_LITERAL); + assert!(lexer.has_preceding_line_break()); +} + +#[test] +fn rewind_restores_the_current_flags() { + let mut lexer = RLexer::from_str("1\n2 3"); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_DOUBLE_LITERAL); + assert_eq!(lexer.next_token(RLexContext::Regular), NEWLINE); + assert_eq!(lexer.next_token(RLexContext::Regular), R_DOUBLE_LITERAL); + let checkpoint = lexer.checkpoint(); + + assert_eq!(lexer.next_token(RLexContext::Regular), WHITESPACE); + assert_eq!(lexer.next_token(RLexContext::Regular), R_DOUBLE_LITERAL); + assert!(!lexer.has_preceding_line_break()); + + lexer.rewind(checkpoint); + assert!(lexer.has_preceding_line_break()); +} + +#[test] +fn rewind_restores_the_bom_length() { + let mut lexer = RLexer::from_str("\u{FEFF}1"); + + assert_eq!(lexer.next_token(RLexContext::Regular), UNICODE_BOM); + assert_eq!(lexer.unicode_bom_length, 3); + let checkpoint = lexer.checkpoint(); + + assert_eq!(lexer.next_token(RLexContext::Regular), R_DOUBLE_LITERAL); + + lexer.rewind(checkpoint); + assert_eq!(lexer.unicode_bom_length, 3); +} + +/// Diagnostics raised after the checkpoint are dropped, so a rewind cannot leave +/// the parser reporting an error about text it decided not to lex that way +#[test] +fn rewind_truncates_diagnostics() { + let mut lexer = RLexer::from_str("a %in"); + + assert_eq!(lexer.next_token(RLexContext::Regular), IDENT); + assert_eq!(lexer.next_token(RLexContext::Regular), WHITESPACE); + let checkpoint = lexer.checkpoint(); + + assert_eq!(lexer.next_token(RLexContext::Regular), SPECIAL); + assert_eq!(lexer.diagnostics.len(), 1); + + lexer.rewind(checkpoint); + assert_eq!(lexer.diagnostics.len(), 0); +} + +/// The `None` that sends an `r` down the identifier arm instead +#[test] +fn a_name_is_not_a_raw_string_open() { + assert_eq!(RStringDelimiter::try_from_raw_string_open(""), None); + assert_eq!(RStringDelimiter::try_from_raw_string_open("rnorm"), None); + assert_eq!(RStringDelimiter::try_from_raw_string_open("r\"-x("), None); + assert_eq!(RStringDelimiter::try_from_raw_string_open("\""), None); +} diff --git a/crates/air_r_parser/src/lexer/tests/comments.rs b/crates/air_r_parser/src/lexer/tests/comments.rs new file mode 100644 index 00000000..03c4e446 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/comments.rs @@ -0,0 +1,33 @@ +use super::*; + +#[test] +fn to_end_of_line() { + assert_lex!("# comment\n1", COMMENT: 9, NEWLINE: 1, R_DOUBLE_LITERAL: 1); +} + +#[test] +fn ending_the_file_without_a_newline() { + assert_lex!("1 # comment", R_DOUBLE_LITERAL: 1, WHITESPACE: 1, COMMENT: 9); +} + +#[test] +fn empty_comment() { + assert_lex!("#", COMMENT: 1); +} + +/// The `\r` belongs to the `NEWLINE`, not to the comment +#[test] +fn crlf_stays_out_of_the_comment() { + assert_lex!("# x\r\n", COMMENT: 3, NEWLINE: 2); +} + +#[test] +fn non_ascii_inside_a_comment() { + assert_lex!("# héllo", COMMENT: 8); +} + +/// Nothing inside a comment is lexed, however token-shaped it looks +#[test] +fn contents_are_inert() { + assert_lex!("# \"a\" `b` %in% 1L", COMMENT: 17); +} diff --git a/crates/air_r_parser/src/lexer/tests/coverage.rs b/crates/air_r_parser/src/lexer/tests/coverage.rs new file mode 100644 index 00000000..29f94008 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/coverage.rs @@ -0,0 +1,66 @@ +//! Coverage test to ensure every token has lexer coverage + +use super::*; +use biome_rowan::RawSyntaxKind; +use std::collections::HashSet; + +/// Well-formed sources, which between them must produce every lexable kind but +/// `ERROR_TOKEN` and `R_BRACK2`. The test names the gaps for you. +const SOURCES: &[&str] = &[ + "\u{FEFF}", + ";,{}()?~$@\\+/^", + "[ ] [[ ]]", + "<- <<- := -> ->> - = == != < <= > >=", + "| || & && |> * ** : :: ::: !", + "function for in while repeat if else next break", + "TRUE FALSE NULL Inf NaN NA NA_integer_ NA_real_ NA_complex_ NA_character_", + "foo ... ..1 1 1L 1i %in%\n# comment\n", + "\"a\" 'b' r\"(c)\"", +]; + +/// Sources for the kinds no well-formed source can reach. `ERROR_TOKEN` is the +/// only one, and it comes with a diagnostic by construction, so it needs a list +/// of its own rather than a hole in [`SOURCES`]. +const ERROR_SOURCES: &[&str] = &["\u{1}"]; + +/// The kinds only a re-lex can produce. +/// +/// `R_BRACK2` is the only one +fn re_lexed_kinds() -> HashSet { + let mut lexer = RLexer::from_str("]]"); + lexer.next_token(RLexContext::Regular); + HashSet::from([lexer.re_lex(RReLexContext::JoinBrackets)]) +} + +fn is_lexable(kind: RSyntaxKind) -> bool { + !matches!(kind, TOMBSTONE | EOF | __LAST) && (kind as u16) < (R_ROOT as u16) +} + +#[test] +fn every_lexable_kind_is_produced() { + let mut produced = HashSet::new(); + for source in SOURCES { + let (tokens, diagnostics) = lex(source); + assert!( + diagnostics.is_empty(), + "coverage sources are well-formed R; {source:?} lexed with diagnostics", + ); + produced.extend(tokens.into_iter().map(|token| token.kind)); + } + for source in ERROR_SOURCES { + let (tokens, diagnostics) = lex(source); + assert!( + !diagnostics.is_empty(), + "{source:?} is in ERROR_SOURCES but lexed cleanly", + ); + produced.extend(tokens.into_iter().map(|token| token.kind)); + } + produced.extend(re_lexed_kinds()); + + let missing: Vec<_> = (0..RSyntaxKind::__LAST as u16) + .map(|raw| RSyntaxKind::from_raw(RawSyntaxKind(raw))) + .filter(|kind| is_lexable(*kind) && !produced.contains(kind)) + .collect(); + + assert!(missing.is_empty(), "no test lexes: {missing:#?}"); +} diff --git a/crates/air_r_parser/src/lexer/tests/dots.rs b/crates/air_r_parser/src/lexer/tests/dots.rs new file mode 100644 index 00000000..4b65eeac --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/dots.rs @@ -0,0 +1,64 @@ +use super::*; + +#[test] +fn dispatch() { + assert_lex!( + "... ..1 .5 .foo . ..", + DOTS: 3, + WHITESPACE: 1, + DOTDOTI: 3, + WHITESPACE: 1, + R_DOUBLE_LITERAL: 2, + WHITESPACE: 1, + IDENT: 4, + WHITESPACE: 1, + IDENT: 1, + WHITESPACE: 1, + IDENT: 2, + ); +} + +#[test] +fn dot_dot_i_takes_every_digit() { + assert_lex!("..1", DOTDOTI: 3); + assert_lex!("..12", DOTDOTI: 4); +} + +/// Anything the name scanner picks up beyond the `...` or the digits leaves an +/// ordinary identifier, however dot-like the start of it looks +#[test] +fn a_name_that_continues_is_an_identifier() { + assert_lex!("....", IDENT: 4); + assert_lex!("...a", IDENT: 4); + assert_lex!("...1", IDENT: 4); + assert_lex!("..1a", IDENT: 4); + assert_lex!("..1.", IDENT: 4); + assert_lex!("..1e5", IDENT: 5); +} + +/// A digit after the `.` always makes a number, never a name, in R and in +/// tree-sitter-r alike: otherwise `.1L` and `.1i` lex as identifiers +/// (r-lib/tree-sitter-r#190) +#[test] +fn a_digit_after_the_dot_makes_a_number() { + assert_lex!(".5", R_DOUBLE_LITERAL: 2); + assert_lex!(".1L", R_INTEGER_LITERAL: 3); + assert_lex!(".1i", R_COMPLEX_LITERAL: 3); + // The number stops at the first character it cannot use, and a name starts + // there + assert_lex!(".5abc", R_DOUBLE_LITERAL: 2, IDENT: 3); +} + +#[test] +fn dots_in_context() { + assert_lex!( + "f(..., ..1)", + IDENT: 1, + L_PAREN: 1, + DOTS: 3, + COMMA: 1, + WHITESPACE: 1, + DOTDOTI: 3, + R_PAREN: 1, + ); +} diff --git a/crates/air_r_parser/src/lexer/tests/error_token.rs b/crates/air_r_parser/src/lexer/tests/error_token.rs new file mode 100644 index 00000000..bf06a194 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/error_token.rs @@ -0,0 +1,56 @@ +use super::*; + +/// The token spans the whole character, not its first byte. An off-by-one here +/// would split a multi-byte character and break round-tripping. +#[test] +fn spans_the_whole_character() { + assert_lex_err!("\u{1}", ERROR_TOKEN: 1 => "Unexpected character"); + assert_lex_err!("£", ERROR_TOKEN: 2 => "Unexpected character"); + assert_lex_err!("€", ERROR_TOKEN: 3 => "Unexpected character"); + assert_lex_err!("🎉", ERROR_TOKEN: 4 => "Unexpected character"); +} + +/// One token and one diagnostic per character, each naming its own character. +/// Merging a run into a single token would be cheaper to report and worse to +/// read, and it would hide the second character from the parser entirely. +#[test] +fn each_character_is_its_own_token() { + assert_lex_err!( + "€£", + ERROR_TOKEN: 3, ERROR_TOKEN: 2 + => "Unexpected character `€`", "Unexpected character `£`" + ); +} + +/// The lexer does not stop, and the tokens after the error are the ones a clean +/// source would have produced +#[test] +fn lexing_continues_afterwards() { + assert_lex_err!( + "1 + \u{1} + 2", + R_DOUBLE_LITERAL: 1, WHITESPACE: 1, PLUS: 1, WHITESPACE: 1, + ERROR_TOKEN: 1, + WHITESPACE: 1, PLUS: 1, WHITESPACE: 1, R_DOUBLE_LITERAL: 1 + => "Unexpected character" + ); +} + +#[test] +fn breaks_an_identifier_in_two() { + assert_lex_err!( + "a€b", + IDENT: 1, ERROR_TOKEN: 3, IDENT: 1 + => "Unexpected character" + ); +} + +/// An error token is not trivia, so it takes the sticky break flag and clears it +/// rather than passing it along to the next real token +#[test] +fn carries_a_preceding_line_break() { + assert_line_breaks!( + "\n\u{1}1", + ERROR_TOKEN: true, + R_DOUBLE_LITERAL: false, + ); +} diff --git a/crates/air_r_parser/src/lexer/tests/identifiers.rs b/crates/air_r_parser/src/lexer/tests/identifiers.rs new file mode 100644 index 00000000..3822e46f --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/identifiers.rs @@ -0,0 +1,43 @@ +use super::*; + +#[test] +fn plain_names() { + assert_lex!("foo", IDENT: 3); + assert_lex!("a1", IDENT: 2); + assert_lex!("foo.bar", IDENT: 7); + assert_lex!("x_1.y", IDENT: 5); +} + +/// `_` is not a valid way to start an R name, but tree-sitter-r is deliberately +/// lax: a lone `_` is the native pipe placeholder, and lexing `_foo` as one name +/// is what lets a downstream consumer report it as an invalid one. Splitting it +/// into `_` and `foo` would make that check impossible (r-lib/tree-sitter-r#71). +/// +/// TODO: We may consider making this stricter now that we don't rely on tree sitter +#[test] +fn leading_underscore_is_one_name() { + assert_lex!("_", IDENT: 1); + assert_lex!("_foo", IDENT: 4); +} + +#[test] +fn names_may_start_with_a_dot() { + assert_lex!(".", IDENT: 1); + assert_lex!("..", IDENT: 2); + assert_lex!(".foo", IDENT: 4); + assert_lex!("._foo", IDENT: 5); +} + +#[test] +fn unicode_starts_and_continues() { + assert_lex!("café", IDENT: 5); + assert_lex!("μ", IDENT: 2); + assert_lex!("日本語", IDENT: 9); + assert_lex!("aμ1", IDENT: 4); +} + +/// A character that is neither XID_Start nor punctuation starts no token at all +#[test] +fn a_character_that_is_not_an_identifier_start() { + assert_lex_err!("€", ERROR_TOKEN: 3 => "Unexpected character"); +} diff --git a/crates/air_r_parser/src/lexer/tests/keywords.rs b/crates/air_r_parser/src/lexer/tests/keywords.rs new file mode 100644 index 00000000..f44e58aa --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/keywords.rs @@ -0,0 +1,60 @@ +use super::*; + +#[test] +fn control_flow_keywords() { + assert_lex!("function", FUNCTION_KW: 8); + assert_lex!("for", FOR_KW: 3); + assert_lex!("in", IN_KW: 2); + assert_lex!("while", WHILE_KW: 5); + assert_lex!("repeat", REPEAT_KW: 6); + assert_lex!("if", IF_KW: 2); + assert_lex!("else", ELSE_KW: 4); + assert_lex!("next", NEXT_KW: 4); + assert_lex!("break", BREAK_KW: 5); +} + +#[test] +fn constants() { + assert_lex!("TRUE", TRUE_KW: 4); + assert_lex!("FALSE", FALSE_KW: 5); + assert_lex!("NULL", NULL_KW: 4); + assert_lex!("Inf", INF_KW: 3); + assert_lex!("NaN", NAN_KW: 3); +} + +#[test] +fn missing_values() { + assert_lex!("NA", NA_LOGICAL_KW: 2); + assert_lex!("NA_integer_", NA_INTEGER_KW: 11); + assert_lex!("NA_real_", NA_DOUBLE_KW: 8); + assert_lex!("NA_complex_", NA_COMPLEX_KW: 11); + assert_lex!("NA_character_", NA_CHARACTER_KW: 13); +} + +#[test] +fn keywords_are_case_sensitive() { + assert_lex!("If", IDENT: 2); + assert_lex!("Function", IDENT: 8); + assert_lex!("true", IDENT: 4); + assert_lex!("false", IDENT: 5); + assert_lex!("null", IDENT: 4); + assert_lex!("na", IDENT: 2); + assert_lex!("INF", IDENT: 3); +} + +/// Lexing the whole name before consulting the keyword table is what gets these +/// right for free. tree-sitter-r had to fix `elseμ` specifically (r-lib/tree-sitter-r#200). +#[test] +fn a_keyword_prefix_is_not_a_keyword() { + assert_lex!("elseif", IDENT: 6); + assert_lex!("else_idx", IDENT: 8); + assert_lex!("elseμ", IDENT: 6); + assert_lex!("NA_real", IDENT: 7); + assert_lex!("NA_real__", IDENT: 9); +} + +#[test] +fn backticked_keywords_stay_identifiers() { + assert_lex!("`if`", IDENT: 4); + assert_lex!("`TRUE`", IDENT: 6); +} diff --git a/crates/air_r_parser/src/lexer/tests/line_breaks.rs b/crates/air_r_parser/src/lexer/tests/line_breaks.rs new file mode 100644 index 00000000..0216d01a --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/line_breaks.rs @@ -0,0 +1,58 @@ +//! The sticky `PRECEDING_LINE_BREAK` flag. +//! +//! The lexer answers "was there a line break here", which is a lexical fact. The +//! parser decides whether that fact means anything, which in R is statement +//! termination and `else` attachment. + +use super::*; + +#[test] +fn the_first_token_has_no_preceding_break() { + assert_line_breaks!("1", R_DOUBLE_LITERAL: false); + assert_line_breaks!("1 + 2", R_DOUBLE_LITERAL: false, PLUS: false, R_DOUBLE_LITERAL: false); +} + +#[test] +fn a_leading_newline_still_counts() { + assert_line_breaks!("\n1", R_DOUBLE_LITERAL: true); +} + +#[test] +fn set_by_a_newline_and_cleared_by_the_next_real_token() { + assert_line_breaks!( + "1\n2 3", + R_DOUBLE_LITERAL: false, + R_DOUBLE_LITERAL: true, + R_DOUBLE_LITERAL: false, + ); +} + +#[test] +fn survives_whitespace_and_comments() { + // The break is reported on `+`, not on the whitespace that precedes it and + // not on the `2` that follows it + assert_line_breaks!( + "1\n # c\n + 2", + R_DOUBLE_LITERAL: false, + PLUS: true, + R_DOUBLE_LITERAL: false, + ); +} + +#[test] +fn a_trailing_comment_does_not_set_it() { + assert_line_breaks!( + "1 # c\n2", + R_DOUBLE_LITERAL: false, + R_DOUBLE_LITERAL: true, + ); +} + +#[test] +fn strings_report_the_break() { + assert_line_breaks!( + "1\n\"a\"", + R_DOUBLE_LITERAL: false, + R_DOUBLE_QUOTED_STRING_LITERAL: true, + ); +} diff --git a/crates/air_r_parser/src/lexer/tests/numbers.rs b/crates/air_r_parser/src/lexer/tests/numbers.rs new file mode 100644 index 00000000..aea80795 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/numbers.rs @@ -0,0 +1,105 @@ +use super::*; + +#[test] +fn decimals() { + assert_lex!("1", R_DOUBLE_LITERAL: 1); + assert_lex!("123", R_DOUBLE_LITERAL: 3); + assert_lex!("1.", R_DOUBLE_LITERAL: 2); + assert_lex!("1.5", R_DOUBLE_LITERAL: 3); + assert_lex!(".5", R_DOUBLE_LITERAL: 2); +} + +#[test] +fn suffixes() { + assert_lex!("1L", R_INTEGER_LITERAL: 2); + assert_lex!("1i", R_COMPLEX_LITERAL: 2); + assert_lex!("1.5L", R_INTEGER_LITERAL: 4); + assert_lex!("1e5L", R_INTEGER_LITERAL: 4); + assert_lex!("1L(", R_INTEGER_LITERAL: 2, L_PAREN: 1); +} + +/// Suffix is taken greedily +#[test] +fn a_suffix_is_always_taken() { + assert_lex!("1Li", R_INTEGER_LITERAL: 2, IDENT: 1); + assert_lex!("1LL", R_INTEGER_LITERAL: 2, IDENT: 1); + assert_lex!("1iL", R_COMPLEX_LITERAL: 2, IDENT: 1); + assert_lex!("1L_", R_INTEGER_LITERAL: 2, IDENT: 1); + assert_lex!("1LForever", R_INTEGER_LITERAL: 2, IDENT: 7); + assert_lex!("1e5iL", R_COMPLEX_LITERAL: 4, IDENT: 1); + // `.` starts a name of its own here, rather than continuing the literal + assert_lex!("1L.", R_INTEGER_LITERAL: 2, IDENT: 1); + assert_lex!(".1L.", R_INTEGER_LITERAL: 3, IDENT: 1); +} + +#[test] +fn exponents() { + assert_lex!("1e5", R_DOUBLE_LITERAL: 3); + assert_lex!("1e+3", R_DOUBLE_LITERAL: 4); + assert_lex!("1e-3", R_DOUBLE_LITERAL: 4); + assert_lex!("1.e5", R_DOUBLE_LITERAL: 4); +} + +/// An `e` commits to an exponent, so its digits are required +#[test] +fn a_decimal_exponent_with_no_digits() { + assert_lex_err!("1e", R_DOUBLE_LITERAL: 2 => "Unterminated exponent"); + assert_lex_err!("1e+", R_DOUBLE_LITERAL: 3 => "Unterminated exponent"); + assert_lex_err!("1e-", R_DOUBLE_LITERAL: 3 => "Unterminated exponent"); + // The literal still stops where it stopped, so the name after it is intact + assert_lex_err!("1east", R_DOUBLE_LITERAL: 2, IDENT: 3 => "Unterminated exponent"); + assert_lex_err!("1e-a", R_DOUBLE_LITERAL: 3, IDENT: 1 => "Unterminated exponent"); + // A space does not rescue it either, in R or here + assert_lex_err!( + "1e + 2", + R_DOUBLE_LITERAL: 2, WHITESPACE: 1, PLUS: 1, WHITESPACE: 1, R_DOUBLE_LITERAL: 1 + => "Unterminated exponent" + ); +} + +#[test] +fn hexadecimals() { + assert_lex!("0xFF", R_DOUBLE_LITERAL: 4); + assert_lex!("0XFF", R_DOUBLE_LITERAL: 4); + assert_lex!("0xFFL", R_INTEGER_LITERAL: 5); + assert_lex!("0x1.8", R_DOUBLE_LITERAL: 5); + // R accepts a hexadecimal with nothing after the dot, surprisingly + assert_lex!("0x.", R_DOUBLE_LITERAL: 3); + assert_lex!("0x.8", R_DOUBLE_LITERAL: 4); + // `e` is a hexadecimal digit, not the start of a decimal exponent + assert_lex!("0x1e", R_DOUBLE_LITERAL: 4); +} + +#[test] +fn binary_exponents() { + assert_lex!("0x1p3", R_DOUBLE_LITERAL: 5); + assert_lex!("0x.8p1", R_DOUBLE_LITERAL: 6); + assert_lex!("0x1p+3", R_DOUBLE_LITERAL: 6); + assert_lex!("0x1p-3", R_DOUBLE_LITERAL: 6); + assert_lex!("0x.p3", R_DOUBLE_LITERAL: 5); + assert_lex!("0x1.8p3", R_DOUBLE_LITERAL: 7); +} + +/// A `p` commits to a binary exponent +#[test] +fn a_binary_exponent_with_no_digits() { + assert_lex_err!("0x1p", R_DOUBLE_LITERAL: 4 => "Unterminated binary exponent"); + assert_lex_err!("0x1p+", R_DOUBLE_LITERAL: 5 => "Unterminated binary exponent"); + assert_lex_err!("0x1p-", R_DOUBLE_LITERAL: 5 => "Unterminated binary exponent"); + assert_lex_err!("0x.p", R_DOUBLE_LITERAL: 4 => "Unterminated binary exponent"); + assert_lex_err!("0x.8p", R_DOUBLE_LITERAL: 5 => "Unterminated binary exponent"); +} + +/// `0x` commits to a hexadecimal +#[test] +fn a_hexadecimal_with_no_body() { + assert_lex_err!("0x", R_DOUBLE_LITERAL: 2 => "Unterminated hexadecimal"); + assert_lex_err!("0xg", R_DOUBLE_LITERAL: 2, IDENT: 1 => "Unterminated hexadecimal"); + // The body may not start with the binary exponent either + assert_lex_err!("0xp3", R_DOUBLE_LITERAL: 2, IDENT: 2 => "Unterminated hexadecimal"); +} + +#[test] +fn a_second_dot_starts_a_second_literal() { + assert_lex!("1.5.3", R_DOUBLE_LITERAL: 3, R_DOUBLE_LITERAL: 2); +} diff --git a/crates/air_r_parser/src/lexer/tests/puncts.rs b/crates/air_r_parser/src/lexer/tests/puncts.rs new file mode 100644 index 00000000..ded9127b --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/puncts.rs @@ -0,0 +1,111 @@ +use super::*; + +/// None of these is a prefix of a longer token, so they can be written adjacent +#[test] +fn single_byte_puncts() { + assert_lex!( + ";,{}()?~$@\\+/^", + SEMICOLON: 1, + COMMA: 1, + L_CURLY: 1, + R_CURLY: 1, + L_PAREN: 1, + R_PAREN: 1, + WAT: 1, + TILDE: 1, + DOLLAR: 1, + AT: 1, + BACKSLASH: 1, + PLUS: 1, + DIVIDE: 1, + EXPONENTIATE: 1, + ); +} + +#[test] +fn less_than() { + assert_lex!("<", LESS_THAN: 1); + assert_lex!("<=", LESS_THAN_OR_EQUAL_TO: 2); + assert_lex!("<-", ASSIGN: 2); + assert_lex!("<<-", SUPER_ASSIGN: 3); + assert_lex!("<<", LESS_THAN: 1, LESS_THAN: 1); +} + +#[test] +fn greater_than() { + assert_lex!(">", GREATER_THAN: 1); + assert_lex!(">=", GREATER_THAN_OR_EQUAL_TO: 2); + assert_lex!(">>", GREATER_THAN: 1, GREATER_THAN: 1); +} + +#[test] +fn minus() { + assert_lex!("-", MINUS: 1); + assert_lex!("->", ASSIGN_RIGHT: 2); + assert_lex!("->>", SUPER_ASSIGN_RIGHT: 3); +} + +#[test] +fn colon() { + assert_lex!(":", COLON: 1); + assert_lex!("::", COLON2: 2); + assert_lex!(":::", COLON3: 3); + assert_lex!("::::", COLON3: 3, COLON: 1); + assert_lex!(":=", WALRUS: 2); +} + +#[test] +fn star() { + assert_lex!("*", MULTIPLY: 1); + assert_lex!("**", EXPONENTIATE2: 2); + assert_lex!("***", EXPONENTIATE2: 2, MULTIPLY: 1); +} + +#[test] +fn pipes() { + assert_lex!("|", OR: 1); + assert_lex!("||", OR2: 2); + assert_lex!("|>", PIPE: 2); +} + +#[test] +fn ampersands() { + assert_lex!("&", AND: 1); + assert_lex!("&&", AND2: 2); +} + +#[test] +fn equals() { + assert_lex!("=", EQUAL: 1); + assert_lex!("==", EQUAL2: 2); + assert_lex!("===", EQUAL2: 2, EQUAL: 1); +} + +#[test] +fn bang() { + assert_lex!("!", BANG: 1); + assert_lex!("!=", NOT_EQUAL: 2); +} + +#[test] +fn adjacency_changes_the_meaning() { + assert_lex!("1<-2", R_DOUBLE_LITERAL: 1, ASSIGN: 2, R_DOUBLE_LITERAL: 1); + assert_lex!( + "1 < -2", + R_DOUBLE_LITERAL: 1, + WHITESPACE: 1, + LESS_THAN: 1, + WHITESPACE: 1, + MINUS: 1, + R_DOUBLE_LITERAL: 1, + ); +} + +#[test] +fn a_byte_that_starts_no_token() { + assert_lex_err!( + "1\u{1}", + R_DOUBLE_LITERAL: 1, ERROR_TOKEN: 1 + => "Unexpected character" + ); +} diff --git a/crates/air_r_parser/src/lexer/tests/raw_strings.rs b/crates/air_r_parser/src/lexer/tests/raw_strings.rs new file mode 100644 index 00000000..bd428b5a --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/raw_strings.rs @@ -0,0 +1,72 @@ +use super::*; + +#[test] +fn every_bracket_pair() { + assert_lex!("r\"(abc)\"", R_RAW_STRING_LITERAL: 8); + assert_lex!("r\"[abc]\"", R_RAW_STRING_LITERAL: 8); + assert_lex!("r\"{abc}\"", R_RAW_STRING_LITERAL: 8); + assert_lex!("r\"|abc|\"", R_RAW_STRING_LITERAL: 8); +} + +#[test] +fn hyphen_counts() { + assert_lex!("r\"(a)\"", R_RAW_STRING_LITERAL: 6); + assert_lex!("r\"-(a)-\"", R_RAW_STRING_LITERAL: 8); + assert_lex!("r\"---(a)---\"", R_RAW_STRING_LITERAL: 12); + assert_lex!("r\"--|a|--\"", R_RAW_STRING_LITERAL: 10); +} + +#[test] +fn uppercase_prefix_and_single_quotes() { + assert_lex!("R'---[x]---'", R_RAW_STRING_LITERAL: 12); + assert_lex!("R\"(x)\"", R_RAW_STRING_LITERAL: 6); + assert_lex!("R'|x|'", R_RAW_STRING_LITERAL: 6); +} + +/// A closing bracket that turns out not to start the closing sequence is +/// content, and the scan resumes from where the attempt stopped rather than from +/// the byte after the bracket. Getting that wrong makes both of these +/// unterminated (r-lib/tree-sitter-r#162). +#[test] +fn a_bracket_that_does_not_close() { + assert_lex!("r\"(())\"", R_RAW_STRING_LITERAL: 7); + assert_lex!("r\"-())-\"", R_RAW_STRING_LITERAL: 8); + assert_lex!("r\"|||\"", R_RAW_STRING_LITERAL: 6); + assert_lex!("r\"|a|b|\"", R_RAW_STRING_LITERAL: 8); + assert_lex!("r\"-|a|-|-\"", R_RAW_STRING_LITERAL: 10); +} + +/// The bracket matches but the hyphen count does not, so the string never +/// closes and the content runs to the end of the file +#[test] +fn the_hyphen_count_has_to_match() { + assert_lex_err!("r\"-(a)\"", ERROR_TOKEN: 7 => "Unterminated string"); +} + +#[test] +fn unterminated_runs_to_the_end_of_the_file() { + assert_lex_err!("r\"(abc", ERROR_TOKEN: 6 => "Unterminated string"); + assert_lex_err!("r\"|abc", ERROR_TOKEN: 6 => "Unterminated string"); +} + +/// The prefix has to be complete, so anything short of it falls through to the +/// identifier and punctuation arms +#[test] +fn an_incomplete_prefix_is_just_a_name() { + assert_lex!("rnorm", IDENT: 5); + assert_lex!("r + 1", IDENT: 1, WHITESPACE: 1, PLUS: 1, WHITESPACE: 1, R_DOUBLE_LITERAL: 1); + // A quote with no bracket after it: an ordinary name and an ordinary string + assert_lex!("r\"abc\"", IDENT: 1, R_DOUBLE_QUOTED_STRING_LITERAL: 5); + assert_lex!("r\"-abc\"", IDENT: 1, R_DOUBLE_QUOTED_STRING_LITERAL: 6); +} + +/// Escapes are not special inside a raw string +#[test] +fn backslashes_are_literal() { + assert_lex!("r\"(a\\)\"", R_RAW_STRING_LITERAL: 7); +} + +#[test] +fn raw_strings_may_span_newlines() { + assert_lex!("r\"(a\nb)\"", R_RAW_STRING_LITERAL: 8); +} diff --git a/crates/air_r_parser/src/lexer/tests/specials.rs b/crates/air_r_parser/src/lexer/tests/specials.rs new file mode 100644 index 00000000..1fec96e3 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/specials.rs @@ -0,0 +1,53 @@ +use super::*; + +#[test] +fn special_operators() { + assert_lex!("%%", SPECIAL: 2); + assert_lex!("%/%", SPECIAL: 3); + assert_lex!("%in%", SPECIAL: 4); + assert_lex!("%o%", SPECIAL: 3); + assert_lex!("%>%", SPECIAL: 3); +} + +#[test] +fn in_context() { + assert_lex!( + "a %in% b", + IDENT: 1, + WHITESPACE: 1, + SPECIAL: 4, + WHITESPACE: 1, + IDENT: 1, + ); +} + +/// `%foo` is still a `SPECIAL` carrying a diagnostic, not an `ERROR_TOKEN`: the +/// text is a well-formed operator that was cut short, so the parser can still +/// use it. `ERROR_TOKEN` is for characters that start nothing at all. +#[test] +fn stops_at_a_newline() { + assert_lex_err!( + "%foo\n1", + SPECIAL: 4, NEWLINE: 1, R_DOUBLE_LITERAL: 1 + => "Unterminated" + ); +} + +#[test] +fn unterminated_at_end_of_file() { + assert_lex_err!("x %in", IDENT: 1, WHITESPACE: 1, SPECIAL: 3 => "Unterminated"); +} + +/// R's `SpecialValue` does not exclude backslash, so `%a\b%` is a valid operator +/// in R +#[test] +fn a_backslash_is_ordinary_text() { + assert_lex!( + "a %a\\b% b", + IDENT: 1, + WHITESPACE: 1, + SPECIAL: 5, + WHITESPACE: 1, + IDENT: 1, + ); +} diff --git a/crates/air_r_parser/src/lexer/tests/strings.rs b/crates/air_r_parser/src/lexer/tests/strings.rs new file mode 100644 index 00000000..8fc7d136 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/strings.rs @@ -0,0 +1,78 @@ +use super::*; + +#[test] +fn double_quoted() { + assert_lex!("\"a\"", R_DOUBLE_QUOTED_STRING_LITERAL: 3); +} + +#[test] +fn single_quoted() { + assert_lex!("'abc'", R_SINGLE_QUOTED_STRING_LITERAL: 5); +} + +#[test] +fn an_empty_string_is_one_token() { + assert_lex!("\"\"", R_DOUBLE_QUOTED_STRING_LITERAL: 2); + assert_lex!("''", R_SINGLE_QUOTED_STRING_LITERAL: 2); + assert_lex!("r\"()\"", R_RAW_STRING_LITERAL: 5); +} + +#[test] +fn escapes_stay_inside_the_string() { + assert_lex!("\"a\\\"b\"", R_DOUBLE_QUOTED_STRING_LITERAL: 6); + assert_lex!("\"a\\\\\"", R_DOUBLE_QUOTED_STRING_LITERAL: 5); + assert_lex!("'a\\'b'", R_SINGLE_QUOTED_STRING_LITERAL: 6); +} + +#[test] +fn strings_may_span_newlines() { + assert_lex!("\"foo\nbar\"", R_DOUBLE_QUOTED_STRING_LITERAL: 9); +} + +#[test] +fn a_crlf_inside_a_string_is_content() { + assert_lex!( + "\"multiline\r\nstring\"\r\n", + R_DOUBLE_QUOTED_STRING_LITERAL: 19, + NEWLINE: 2 + ); +} + +#[test] +fn the_other_quote_is_inert() { + assert_lex!("\"it's\"", R_DOUBLE_QUOTED_STRING_LITERAL: 6); + assert_lex!("'say \"hi\"'", R_SINGLE_QUOTED_STRING_LITERAL: 10); +} + +#[test] +fn comments_and_operators_are_inert() { + assert_lex!("\"# %in% `a`\"", R_DOUBLE_QUOTED_STRING_LITERAL: 12); +} + +/// There is no closing delimiter, so the token cannot claim to be a string. What +/// keeps the error useful is that it points at the opening delimiter rather than +/// highlighting the rest of the file. +#[test] +fn unterminated_runs_to_the_end_of_the_file() { + assert_lex_err!("\"abc", ERROR_TOKEN: 4 => "Unterminated string"); + assert_lex_err!("\"", ERROR_TOKEN: 1 => "Unterminated string"); + assert_lex_err!("'a\nb", ERROR_TOKEN: 4 => "Unterminated string"); +} + +/// A backslash at the very end of the file must not read past it +#[test] +fn a_trailing_escape_does_not_run_past_the_end() { + assert_lex_err!("\"a\\", ERROR_TOKEN: 3 => "Unterminated string"); +} + +#[test] +fn in_context() { + assert_lex!( + "x <- \"a\"", + IDENT: 1, + WHITESPACE: 1, + ASSIGN: 2, + WHITESPACE: 1, + R_DOUBLE_QUOTED_STRING_LITERAL: 3, + ); +} diff --git a/crates/air_r_parser/src/lexer/tests/whitespace.rs b/crates/air_r_parser/src/lexer/tests/whitespace.rs new file mode 100644 index 00000000..c90b26f4 --- /dev/null +++ b/crates/air_r_parser/src/lexer/tests/whitespace.rs @@ -0,0 +1,30 @@ +use super::*; + +#[test] +fn runs_coalesce() { + assert_lex!(" ", WHITESPACE: 3); + assert_lex!(" \t\u{0B}\u{0C} ", WHITESPACE: 5); +} + +#[test] +fn whitespace_stops_at_a_line_break() { + assert_lex!(" \n ", WHITESPACE: 2, NEWLINE: 1, WHITESPACE: 2); +} + +#[test] +fn line_feed_and_lone_carriage_return() { + assert_lex!("\n", NEWLINE: 1); + assert_lex!("\r", NEWLINE: 1); +} + +#[test] +fn crlf_is_one_newline() { + assert_lex!("\r\n", NEWLINE: 2); +} + +#[test] +fn each_line_break_is_its_own_token() { + assert_lex!("\n\n", NEWLINE: 1, NEWLINE: 1); + // The first `\r` stands alone; only the second pairs with the `\n` + assert_lex!("\r\r\n", NEWLINE: 1, NEWLINE: 2); +} diff --git a/crates/air_r_parser/src/lib.rs b/crates/air_r_parser/src/lib.rs index 84d72fda..3968be31 100644 --- a/crates/air_r_parser/src/lib.rs +++ b/crates/air_r_parser/src/lib.rs @@ -1,14 +1,16 @@ +mod diagnostics; +mod lexer; mod options; mod parse; +mod parser; +mod syntax; +mod token_source; -#[allow(unused)] -mod treesitter; +pub use crate::options::RParserOptions; +pub use crate::parse::RParse; +pub use crate::parse::parse; use air_r_factory::RSyntaxFactory; -pub use options::RParserOptions; -pub use parse::RParse; -pub use parse::parse; - use air_r_syntax::RLanguage; use biome_parser::tree_sink::LosslessTreeSink; diff --git a/crates/air_r_parser/src/parse.rs b/crates/air_r_parser/src/parse.rs index 95d2ba18..681bfbf6 100644 --- a/crates/air_r_parser/src/parse.rs +++ b/crates/air_r_parser/src/parse.rs @@ -1,24 +1,13 @@ +use crate::RLosslessTreeSink; +use crate::options::RParserOptions; +use crate::parser::RParser; use air_r_syntax::RRoot; -use air_r_syntax::RSyntaxKind; use air_r_syntax::RSyntaxNode; use biome_parser::AnyParse; use biome_parser::diagnostic::ParseDiagnostic; -use biome_parser::event::Event; -use biome_parser::prelude::Trivia; use biome_rowan::AstNode; use biome_rowan::NodeCache; use biome_rowan::SendNode; -use biome_rowan::TextRange; -use biome_rowan::TextSize; -use biome_rowan::TriviaPieceKind; -use biome_unicode_table::Dispatch; -use tree_sitter::Tree; - -use crate::RLosslessTreeSink; -use crate::RParserOptions; -use crate::treesitter::NodeTypeExt; -use crate::treesitter::Preorder; -use crate::treesitter::WalkEvent; /// A utility struct for managing the result of a parser job /// @@ -93,1522 +82,86 @@ impl From for AnyParse { /// Parses `text` pub fn parse(text: &str, options: RParserOptions) -> RParse { let mut cache = NodeCache::default(); - let (events, trivia, diagnostics) = parse_text(text, options); - build_tree(text, events, trivia, diagnostics, &mut cache) -} - -fn build_tree( - text: &str, - events: Vec>, - trivia: Vec, - diagnostics: Vec, - cache: &mut NodeCache, -) -> RParse { - tracing::debug_span!("parse").in_scope(move || { - let mut tree_sink = RLosslessTreeSink::with_cache(text, &trivia, cache); - biome_parser::event::process(&mut tree_sink, events, diagnostics); - let (green, diagnostics) = tree_sink.finish(); - - RParse::new(green, diagnostics) - }) -} - -fn parse_text( - text: &str, - _options: RParserOptions, -) -> (Vec>, Vec, Vec) { - let mut parser = tree_sitter::Parser::new(); - parser - .set_language(&tree_sitter_r::LANGUAGE.into()) - .unwrap(); - - let ast = parser.parse(text, None).unwrap(); - parse_from_treesitter(&ast, text) + parse_with_cache(text, options, &mut cache) } -fn parse_from_treesitter( - ast: &Tree, - text: &str, -) -> (Vec>, Vec, Vec) { - if ast.root_node().has_error() { - // TODO: In the long term we want an error resiliant parser. - // This would probably only be able to happen if we swap out tree sitter - // for a hand written recursive descent pratt parser using the Biome infra. - return parse_failure(text); - } - - let mut walker = RWalk::new(text); +/// Parses `text`, structurally sharing what it can with the trees already in +/// `cache` +fn parse_with_cache(text: &str, options: RParserOptions, cache: &mut NodeCache) -> RParse { + let mut parser = RParser::new(text, options); + crate::syntax::parse_root(&mut parser); + let (events, trivia, diagnostics) = parser.finish(); - let root = ast.root_node(); - let mut iter = root.preorder(); - walker.walk(&mut iter); + let mut tree_sink = RLosslessTreeSink::with_cache(text, &trivia, cache); + biome_parser::event::process(&mut tree_sink, events, diagnostics); + let (green, diagnostics) = tree_sink.finish(); - let (events, trivia) = walker.events.drain(); - - (events, trivia, Vec::new()) -} - -fn parse_failure(text: &str) -> (Vec>, Vec, Vec) { - // Safety: We don't allow files longer than a `u32` can support. - let end = - TextSize::try_from(text.len()).expect("`text` can't be longer than `TextSize` allows."); - - // Must provide a valid root node on failures, otherwise `tree_sink.finish()` fails. - // An `R_ROOT` must have an `R_STATEMENT_LIST`, and we force that to hold 1 expression - // statement with a bogus expression that spans the whole file. This allows the AST printer to - // work correctly in error tests, and during debugging. - let events = vec![ - Event::Start { - kind: RSyntaxKind::R_ROOT, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_STATEMENT_LIST, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_EXPRESSION_STATEMENT, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_BOGUS_EXPRESSION, - forward_parent: None, - }, - Event::Token { - kind: RSyntaxKind::R_BOGUS, - end, - }, - Event::Finish, - Event::Finish, - Event::Finish, - Event::Finish, - ]; - - // No trivia - let trivia = vec![]; - - // Single whole-file diagnostic for now - let span: Option = None; - let diagnostic = ParseDiagnostic::new("Failed to parse due to syntax errors.", span); - - (events, trivia, vec![diagnostic]) -} - -/// Given an ast with absolutely no ERROR or MISSING nodes, let's walk that tree -/// and collect our `trivia` and `events`. -struct RWalk<'src> { - text: &'src str, - events: RWalkEvents, - last_end: TextSize, -} - -impl<'src> RWalk<'src> { - fn new(text: &'src str) -> Self { - Self { - text, - events: RWalkEvents::new(), - last_end: TextSize::from(0), - } - } - - fn walk(&mut self, iter: &mut Preorder) { - while let Some(event) = iter.next() { - match event { - WalkEvent::Enter(node) => self.handle_enter(node, node.syntax_kind(), iter), - WalkEvent::Leave(node) => self.handle_leave(node, node.syntax_kind()), - } - } - } - - /// Walk only the upcoming node, including its subtree - /// - /// If the `next()` event is an `Enter`, `walk_next()` walks the - /// `node` returned in the `Enter` event until hitting the `Leave` event - /// of that `node`. - fn walk_next(&mut self, iter: &mut Preorder) { - let end = match iter.peek() { - Some(end) => match end { - WalkEvent::Enter(end) => *end, - WalkEvent::Leave(_) => return, - }, - None => return, - }; - - while let Some(event) = iter.next() { - match event { - WalkEvent::Enter(node) => self.handle_enter(node, node.syntax_kind(), iter), - WalkEvent::Leave(node) => { - self.handle_leave(node, node.syntax_kind()); - if node == end { - break; - } - } - } - } - } - - fn handle_enter(&mut self, node: tree_sitter::Node, kind: RSyntaxKind, iter: &mut Preorder) { - match kind { - RSyntaxKind::R_ROOT => self.handle_root_enter(node, iter), - - RSyntaxKind::R_UNARY_EXPRESSION - | RSyntaxKind::R_BINARY_EXPRESSION - | RSyntaxKind::R_FUNCTION_DEFINITION - | RSyntaxKind::R_FOR_STATEMENT - | RSyntaxKind::R_WHILE_STATEMENT - | RSyntaxKind::R_REPEAT_STATEMENT - | RSyntaxKind::R_PARENTHESIZED_EXPRESSION - | RSyntaxKind::R_EXTRACT_EXPRESSION - | RSyntaxKind::R_NAMESPACE_EXPRESSION - | RSyntaxKind::R_NA_EXPRESSION => self.handle_node_enter(kind), - - RSyntaxKind::R_CALL => self.handle_call_like_enter( - RSyntaxKind::R_CALL, - RSyntaxKind::R_CALL_ARGUMENTS, - node, - iter, - ), - RSyntaxKind::R_SUBSET => self.handle_call_like_enter( - RSyntaxKind::R_SUBSET, - RSyntaxKind::R_SUBSET_ARGUMENTS, - node, - iter, - ), - RSyntaxKind::R_SUBSET2 => self.handle_call_like_enter( - RSyntaxKind::R_SUBSET2, - RSyntaxKind::R_SUBSET2_ARGUMENTS, - node, - iter, - ), - RSyntaxKind::R_PARAMETERS => self.handle_parameters_enter(node, iter), - RSyntaxKind::R_PARAMETER => self.handle_parameter_enter(node, iter), - RSyntaxKind::R_IF_STATEMENT => self.handle_if_statement_enter(node, iter), - RSyntaxKind::R_ARGUMENT => self.handle_argument_enter(node, iter), - RSyntaxKind::R_BRACED_STATEMENTS => self.handle_braced_statements_enter(node, iter), - - // Literals / wrapped keywords - RSyntaxKind::R_DOUBLE_VALUE - | RSyntaxKind::R_IDENTIFIER - | RSyntaxKind::R_DOT_DOT_I - | RSyntaxKind::R_DOTS - | RSyntaxKind::R_NEXT_EXPRESSION - | RSyntaxKind::R_BREAK_EXPRESSION - | RSyntaxKind::R_TRUE_EXPRESSION - | RSyntaxKind::R_FALSE_EXPRESSION - | RSyntaxKind::R_NULL_EXPRESSION - | RSyntaxKind::R_INF_EXPRESSION - | RSyntaxKind::R_NAN_EXPRESSION => self.handle_value_enter(kind), - - RSyntaxKind::R_INTEGER_VALUE => self.handle_integer_value_enter(iter), - RSyntaxKind::R_COMPLEX_VALUE => self.handle_complex_value_enter(iter), - RSyntaxKind::R_STRING_VALUE => self.handle_string_value_enter(iter), - - // Tokens are no-ops on `Enter`, handled on `Leave` - RSyntaxKind::SEMICOLON - | RSyntaxKind::COMMA - | RSyntaxKind::TILDE - | RSyntaxKind::ASSIGN - | RSyntaxKind::SUPER_ASSIGN - | RSyntaxKind::WALRUS - | RSyntaxKind::ASSIGN_RIGHT - | RSyntaxKind::SUPER_ASSIGN_RIGHT - | RSyntaxKind::EQUAL - | RSyntaxKind::OR - | RSyntaxKind::AND - | RSyntaxKind::OR2 - | RSyntaxKind::AND2 - | RSyntaxKind::LESS_THAN - | RSyntaxKind::LESS_THAN_OR_EQUAL_TO - | RSyntaxKind::GREATER_THAN - | RSyntaxKind::GREATER_THAN_OR_EQUAL_TO - | RSyntaxKind::EQUAL2 - | RSyntaxKind::NOT_EQUAL - | RSyntaxKind::PLUS - | RSyntaxKind::MINUS - | RSyntaxKind::MULTIPLY - | RSyntaxKind::DIVIDE - | RSyntaxKind::EXPONENTIATE - | RSyntaxKind::EXPONENTIATE2 - | RSyntaxKind::PIPE - | RSyntaxKind::SPECIAL - | RSyntaxKind::COLON - | RSyntaxKind::COLON2 - | RSyntaxKind::COLON3 - | RSyntaxKind::DOLLAR - | RSyntaxKind::AT - | RSyntaxKind::BANG - | RSyntaxKind::WAT - | RSyntaxKind::BACKSLASH - | RSyntaxKind::FUNCTION_KW - | RSyntaxKind::FOR_KW - | RSyntaxKind::IN_KW - | RSyntaxKind::WHILE_KW - | RSyntaxKind::REPEAT_KW - | RSyntaxKind::IF_KW - | RSyntaxKind::ELSE_KW - | RSyntaxKind::NA_LOGICAL_KW - | RSyntaxKind::NA_INTEGER_KW - | RSyntaxKind::NA_DOUBLE_KW - | RSyntaxKind::NA_COMPLEX_KW - | RSyntaxKind::NA_CHARACTER_KW - | RSyntaxKind::L_PAREN - | RSyntaxKind::R_PAREN - | RSyntaxKind::L_BRACK - | RSyntaxKind::R_BRACK - | RSyntaxKind::L_BRACK2 - | RSyntaxKind::R_BRACK2 - | RSyntaxKind::L_CURLY - | RSyntaxKind::R_CURLY => (), - - // Comments - RSyntaxKind::COMMENT => self.handle_comment_enter(), - - // Unreachable - RSyntaxKind::R_ELSE_CLAUSE - | RSyntaxKind::R_CALL_ARGUMENTS - | RSyntaxKind::R_SUBSET_ARGUMENTS - | RSyntaxKind::R_SUBSET2_ARGUMENTS - | RSyntaxKind::R_ARGUMENT_NAME_CLAUSE - | RSyntaxKind::R_PARAMETER_LIST - | RSyntaxKind::R_ARGUMENT_LIST - | RSyntaxKind::R_STATEMENT_LIST - | RSyntaxKind::R_EXPRESSION_STATEMENT - | RSyntaxKind::R_EMPTY_STATEMENT - | RSyntaxKind::R_PARAMETER_DEFAULT - | RSyntaxKind::EOF - | RSyntaxKind::UNICODE_BOM - | RSyntaxKind::DOTS - | RSyntaxKind::R_INTEGER_LITERAL - | RSyntaxKind::R_DOUBLE_LITERAL - | RSyntaxKind::R_COMPLEX_LITERAL - | RSyntaxKind::R_SINGLE_QUOTED_STRING_LITERAL - | RSyntaxKind::R_DOUBLE_QUOTED_STRING_LITERAL - | RSyntaxKind::R_RAW_STRING_LITERAL - | RSyntaxKind::NEWLINE - | RSyntaxKind::WHITESPACE - | RSyntaxKind::IDENT - | RSyntaxKind::DOTDOTI - | RSyntaxKind::ERROR_TOKEN - | RSyntaxKind::NEXT_KW - | RSyntaxKind::BREAK_KW - | RSyntaxKind::TRUE_KW - | RSyntaxKind::FALSE_KW - | RSyntaxKind::NULL_KW - | RSyntaxKind::INF_KW - | RSyntaxKind::NAN_KW - | RSyntaxKind::R_BOGUS - | RSyntaxKind::R_BOGUS_VALUE - | RSyntaxKind::R_BOGUS_EXPRESSION - | RSyntaxKind::R_BOGUS_SELECTOR - | RSyntaxKind::R_BOGUS_PARAMETER_NAME - | RSyntaxKind::R_BOGUS_FOR_VARIABLE - | RSyntaxKind::TOMBSTONE - | RSyntaxKind::__LAST => unreachable!("{kind:?}"), - } - } - - fn handle_leave(&mut self, node: tree_sitter::Node, kind: RSyntaxKind) { - match kind { - RSyntaxKind::R_ROOT => self.handle_root_leave(node), - - RSyntaxKind::R_UNARY_EXPRESSION - | RSyntaxKind::R_BINARY_EXPRESSION - | RSyntaxKind::R_FUNCTION_DEFINITION - | RSyntaxKind::R_FOR_STATEMENT - | RSyntaxKind::R_WHILE_STATEMENT - | RSyntaxKind::R_REPEAT_STATEMENT - | RSyntaxKind::R_PARENTHESIZED_EXPRESSION - | RSyntaxKind::R_EXTRACT_EXPRESSION - | RSyntaxKind::R_NAMESPACE_EXPRESSION - | RSyntaxKind::R_NA_EXPRESSION => self.handle_node_leave(kind), - - RSyntaxKind::R_CALL => self.handle_call_like_leave(kind), - RSyntaxKind::R_SUBSET => self.handle_call_like_leave(kind), - RSyntaxKind::R_SUBSET2 => self.handle_call_like_leave(kind), - RSyntaxKind::R_PARAMETERS => self.handle_parameters_leave(), - RSyntaxKind::R_PARAMETER => self.handle_parameter_leave(), - RSyntaxKind::R_IF_STATEMENT => self.handle_if_statement_leave(), - RSyntaxKind::R_ARGUMENT => self.handle_argument_leave(), - RSyntaxKind::R_BRACED_STATEMENTS => self.handle_braced_statements_leave(), - - // Literals / wrapped keywords - RSyntaxKind::R_DOUBLE_VALUE => { - self.handle_value_leave(node, kind, RSyntaxKind::R_DOUBLE_LITERAL) - } - RSyntaxKind::R_IDENTIFIER => self.handle_value_leave(node, kind, RSyntaxKind::IDENT), - RSyntaxKind::R_DOTS => self.handle_value_leave(node, kind, RSyntaxKind::DOTS), - RSyntaxKind::R_DOT_DOT_I => self.handle_value_leave(node, kind, RSyntaxKind::DOTDOTI), - RSyntaxKind::R_NEXT_EXPRESSION => { - self.handle_value_leave(node, kind, RSyntaxKind::NEXT_KW) - } - RSyntaxKind::R_BREAK_EXPRESSION => { - self.handle_value_leave(node, kind, RSyntaxKind::BREAK_KW) - } - RSyntaxKind::R_TRUE_EXPRESSION => { - self.handle_value_leave(node, kind, RSyntaxKind::TRUE_KW) - } - RSyntaxKind::R_FALSE_EXPRESSION => { - self.handle_value_leave(node, kind, RSyntaxKind::FALSE_KW) - } - RSyntaxKind::R_NULL_EXPRESSION => { - self.handle_value_leave(node, kind, RSyntaxKind::NULL_KW) - } - RSyntaxKind::R_INF_EXPRESSION => { - self.handle_value_leave(node, kind, RSyntaxKind::INF_KW) - } - RSyntaxKind::R_NAN_EXPRESSION => { - self.handle_value_leave(node, kind, RSyntaxKind::NAN_KW) - } - RSyntaxKind::R_INTEGER_VALUE => self.handle_integer_value_leave(node), - RSyntaxKind::R_COMPLEX_VALUE => self.handle_complex_value_leave(node), - RSyntaxKind::R_STRING_VALUE => self.handle_string_value_leave(node), - - // Tokens - RSyntaxKind::SEMICOLON - | RSyntaxKind::COMMA - | RSyntaxKind::TILDE - | RSyntaxKind::ASSIGN - | RSyntaxKind::SUPER_ASSIGN - | RSyntaxKind::WALRUS - | RSyntaxKind::ASSIGN_RIGHT - | RSyntaxKind::SUPER_ASSIGN_RIGHT - | RSyntaxKind::EQUAL - | RSyntaxKind::OR - | RSyntaxKind::AND - | RSyntaxKind::OR2 - | RSyntaxKind::AND2 - | RSyntaxKind::LESS_THAN - | RSyntaxKind::LESS_THAN_OR_EQUAL_TO - | RSyntaxKind::GREATER_THAN - | RSyntaxKind::GREATER_THAN_OR_EQUAL_TO - | RSyntaxKind::EQUAL2 - | RSyntaxKind::NOT_EQUAL - | RSyntaxKind::PLUS - | RSyntaxKind::MINUS - | RSyntaxKind::MULTIPLY - | RSyntaxKind::DIVIDE - | RSyntaxKind::EXPONENTIATE - | RSyntaxKind::EXPONENTIATE2 - | RSyntaxKind::PIPE - | RSyntaxKind::SPECIAL - | RSyntaxKind::COLON - | RSyntaxKind::COLON2 - | RSyntaxKind::COLON3 - | RSyntaxKind::DOLLAR - | RSyntaxKind::AT - | RSyntaxKind::BANG - | RSyntaxKind::WAT - | RSyntaxKind::BACKSLASH - | RSyntaxKind::FUNCTION_KW - | RSyntaxKind::FOR_KW - | RSyntaxKind::IN_KW - | RSyntaxKind::WHILE_KW - | RSyntaxKind::REPEAT_KW - | RSyntaxKind::IF_KW - | RSyntaxKind::ELSE_KW - | RSyntaxKind::NA_LOGICAL_KW - | RSyntaxKind::NA_INTEGER_KW - | RSyntaxKind::NA_DOUBLE_KW - | RSyntaxKind::NA_COMPLEX_KW - | RSyntaxKind::NA_CHARACTER_KW - | RSyntaxKind::L_PAREN - | RSyntaxKind::R_PAREN - | RSyntaxKind::L_BRACK - | RSyntaxKind::R_BRACK - | RSyntaxKind::L_BRACK2 - | RSyntaxKind::R_BRACK2 - | RSyntaxKind::L_CURLY - | RSyntaxKind::R_CURLY => self.handle_token(node, kind), - - // Comments - RSyntaxKind::COMMENT => self.handle_comment_leave(node), - - // Unreachable directly - RSyntaxKind::R_ELSE_CLAUSE - | RSyntaxKind::R_CALL_ARGUMENTS - | RSyntaxKind::R_SUBSET_ARGUMENTS - | RSyntaxKind::R_SUBSET2_ARGUMENTS - | RSyntaxKind::R_ARGUMENT_NAME_CLAUSE - | RSyntaxKind::R_PARAMETER_LIST - | RSyntaxKind::R_ARGUMENT_LIST - | RSyntaxKind::R_STATEMENT_LIST - | RSyntaxKind::R_EXPRESSION_STATEMENT - | RSyntaxKind::R_EMPTY_STATEMENT - | RSyntaxKind::R_PARAMETER_DEFAULT - | RSyntaxKind::EOF - | RSyntaxKind::UNICODE_BOM - | RSyntaxKind::DOTS - | RSyntaxKind::DOTDOTI - | RSyntaxKind::R_INTEGER_LITERAL - | RSyntaxKind::R_DOUBLE_LITERAL - | RSyntaxKind::R_COMPLEX_LITERAL - | RSyntaxKind::R_SINGLE_QUOTED_STRING_LITERAL - | RSyntaxKind::R_DOUBLE_QUOTED_STRING_LITERAL - | RSyntaxKind::R_RAW_STRING_LITERAL - | RSyntaxKind::NEWLINE - | RSyntaxKind::WHITESPACE - | RSyntaxKind::IDENT - | RSyntaxKind::ERROR_TOKEN - | RSyntaxKind::NEXT_KW - | RSyntaxKind::BREAK_KW - | RSyntaxKind::TRUE_KW - | RSyntaxKind::FALSE_KW - | RSyntaxKind::NULL_KW - | RSyntaxKind::INF_KW - | RSyntaxKind::NAN_KW - | RSyntaxKind::R_BOGUS - | RSyntaxKind::R_BOGUS_VALUE - | RSyntaxKind::R_BOGUS_EXPRESSION - | RSyntaxKind::R_BOGUS_SELECTOR - | RSyntaxKind::R_BOGUS_PARAMETER_NAME - | RSyntaxKind::R_BOGUS_FOR_VARIABLE - | RSyntaxKind::TOMBSTONE - | RSyntaxKind::__LAST => unreachable!("{kind:?}"), - } - } - - fn handle_node_enter(&mut self, kind: RSyntaxKind) { - self.events.start(kind); - } - - // `_kind` is nice to see at call sites as it makes the code more self-expanatory - fn handle_node_leave(&mut self, _kind: RSyntaxKind) { - self.events.finish(); - } - - fn handle_value_enter(&mut self, kind: RSyntaxKind) { - self.handle_node_enter(kind); - } - - fn handle_value_leave( - &mut self, - node: tree_sitter::Node, - kind: RSyntaxKind, - literal_kind: RSyntaxKind, - ) { - // Push the token for the literal - self.handle_token(node, literal_kind); - - // Then close the node - self.handle_node_leave(kind); - } - - fn handle_root_enter(&mut self, node: tree_sitter::Node, iter: &mut Preorder) { - // Start the overarching root - self.handle_node_enter(RSyntaxKind::R_ROOT); - - // TODO: Handle optional BOM? - - self.handle_node_enter(RSyntaxKind::R_STATEMENT_LIST); - - // Root contains a list of `statements`, so uses the same - // style loop as `handle_braced_statements_enter()` - while let Some(event) = iter.peek() { - match event { - WalkEvent::Enter(next) => match next.syntax_kind() { - RSyntaxKind::COMMENT => self.walk_next(iter), - _ => self.handle_statement(iter), - }, - WalkEvent::Leave(next) => { - if node != *next { - panic!("Expected next `Leave` event to be for `node`."); - } - break; - } - } - } - } - - fn handle_root_leave(&mut self, node: tree_sitter::Node) { - // Finish statement list - self.handle_node_leave(RSyntaxKind::R_STATEMENT_LIST); - - // Safety: tree-sitter uses `u32` internally and upcasts to `usize` in - // `end_byte()`. `TextSize` also uses `u32` internally, so we are safe. - let this_end = TextSize::try_from(node.end_byte()).unwrap(); - let gap = &self.text[usize::from(self.last_end)..usize::from(this_end)]; - - // Derive trivia between last token and end of document. Leading - // trivia here belongs to the `EOF` token, which `TreeSink` adds for us. - self.events.derive_trivia(gap, self.last_end); - - self.handle_node_leave(RSyntaxKind::R_ROOT); - } - - fn handle_token(&mut self, node: tree_sitter::Node, kind: RSyntaxKind) { - // Safety: tree-sitter uses `u32` internally and upcasts to `usize` in - // `start/end_byte()`. `TextSize` also uses `u32` internally, so we are safe. - let this_start = TextSize::try_from(node.start_byte()).unwrap(); - let this_end = TextSize::try_from(node.end_byte()).unwrap(); - let gap = &self.text[usize::from(self.last_end)..usize::from(this_start)]; - - self.events.derive_trivia(gap, self.last_end); - - self.events.token(kind, this_end); - - self.last_end = this_end; - } - - fn handle_comment_enter(&mut self) { - // Nothing, handled on `Leave` - } - - fn handle_comment_leave(&mut self, node: tree_sitter::Node) { - let this_start = TextSize::try_from(node.start_byte()).unwrap(); - let this_end = TextSize::try_from(node.end_byte()).unwrap(); - let gap = &self.text[usize::from(self.last_end)..usize::from(this_start)]; - - self.events.derive_trivia(gap, self.last_end); - - self.events.comment(TextRange::new(this_start, this_end)); - - self.last_end = this_end; - } - - fn handle_parameters_enter(&mut self, node: tree_sitter::Node, iter: &mut Preorder) { - self.handle_node_enter(RSyntaxKind::R_PARAMETERS); - - // Regardless of whether or not we see an `R_PARAMETER`, we have to - // open and close the required `R_PARAMETER_LIST` - while let Some(event) = iter.peek() { - match event { - WalkEvent::Enter(next) => match next.syntax_kind() { - RSyntaxKind::L_PAREN => { - self.walk_next(iter); - self.handle_node_enter(RSyntaxKind::R_PARAMETER_LIST); - } - RSyntaxKind::R_PAREN => { - self.handle_node_leave(RSyntaxKind::R_PARAMETER_LIST); - self.walk_next(iter); - } - RSyntaxKind::R_PARAMETER | RSyntaxKind::COMMA | RSyntaxKind::COMMENT => { - self.walk_next(iter) - } - kind => unreachable!("{kind:?}"), - }, - WalkEvent::Leave(next) => { - if node != *next { - panic!("Expected next `Leave` event to be for `node`."); - } - break; - } - } - } - } - - fn handle_parameters_leave(&mut self) { - self.handle_node_leave(RSyntaxKind::R_PARAMETERS); - } - - fn handle_parameter_enter(&mut self, node: tree_sitter::Node, iter: &mut Preorder) { - self.handle_node_enter(RSyntaxKind::R_PARAMETER); - - // Seeing an `=` causes us to open an `R_PARAMETER_DEFAULT` node which - // we push the `=` keyword under, along with the remaining `value` for - // the default, and any comments that appear there. We then close the - // `R_PARAMETER_DEFAULT` on the way out. - let mut used_equal = false; - - while let Some(event) = iter.peek() { - match event { - WalkEvent::Enter(next) => match next.syntax_kind() { - RSyntaxKind::EQUAL => { - used_equal = true; - self.handle_node_enter(RSyntaxKind::R_PARAMETER_DEFAULT); - self.walk_next(iter); - } - // Main loop handles everything else - _ => self.walk_next(iter), - }, - WalkEvent::Leave(next) => { - if node != *next { - panic!("Expected next `Leave` event to be for `node`."); - } - break; - } - } - } - - if used_equal { - self.handle_node_leave(RSyntaxKind::R_PARAMETER_DEFAULT); - } - } - - fn handle_parameter_leave(&mut self) { - self.handle_node_leave(RSyntaxKind::R_PARAMETER); - } - - fn handle_integer_value_enter(&mut self, iter: &mut Preorder) { - // Skip subtree, we don't want to separate the literal from the `L`. - // Can't have comments between the value and the `L`. - iter.skip_subtree(); - self.handle_value_enter(RSyntaxKind::R_INTEGER_VALUE); - } - - fn handle_integer_value_leave(&mut self, node: tree_sitter::Node) { - self.handle_value_leave( - node, - RSyntaxKind::R_INTEGER_VALUE, - RSyntaxKind::R_INTEGER_LITERAL, - ); - } - - fn handle_complex_value_enter(&mut self, iter: &mut Preorder) { - // Skip subtree, we don't want to separate the literal from the `i`. - // Can't have comments between the value and the `i`. - iter.skip_subtree(); - self.handle_value_enter(RSyntaxKind::R_COMPLEX_VALUE); - } - - fn handle_complex_value_leave(&mut self, node: tree_sitter::Node) { - self.handle_value_leave( - node, - RSyntaxKind::R_COMPLEX_VALUE, - RSyntaxKind::R_COMPLEX_LITERAL, - ); - } - - fn handle_string_value_enter(&mut self, iter: &mut Preorder) { - // Skip subtree, the delimiters and content are one token. - // Can't have comments in a string subtree. - iter.skip_subtree(); - self.handle_value_enter(RSyntaxKind::R_STRING_VALUE); - } - - fn handle_string_value_leave(&mut self, node: tree_sitter::Node) { - let literal_kind = match self.text.as_bytes()[node.start_byte()] { - b'"' => RSyntaxKind::R_DOUBLE_QUOTED_STRING_LITERAL, - b'\'' => RSyntaxKind::R_SINGLE_QUOTED_STRING_LITERAL, - b'r' | b'R' => RSyntaxKind::R_RAW_STRING_LITERAL, - byte => unreachable!("Unexpected string start {:?}", char::from(byte)), - }; - self.handle_value_leave(node, RSyntaxKind::R_STRING_VALUE, literal_kind); - } - - fn handle_if_statement_enter(&mut self, node: tree_sitter::Node, iter: &mut Preorder) { - self.handle_node_enter(RSyntaxKind::R_IF_STATEMENT); - - // Seeing an `else` causes us to open an `R_ELSE_CLAUSE` node which - // we push the `else` keyword under, along with the `alternative`, - // and any comments that appear there. We then close the `R_ELSE_CLAUSE` - // on the way out. - let mut used_else = false; - - while let Some(event) = iter.peek() { - match event { - WalkEvent::Enter(next) => match next.syntax_kind() { - RSyntaxKind::IF_KW => self.walk_next(iter), - RSyntaxKind::L_PAREN => self.walk_next(iter), - RSyntaxKind::R_PAREN => self.walk_next(iter), - RSyntaxKind::ELSE_KW => { - used_else = true; - self.handle_node_enter(RSyntaxKind::R_ELSE_CLAUSE); - self.walk_next(iter); - } - RSyntaxKind::COMMENT => self.walk_next(iter), - // i.e. the `condition`, `consequence`, and `alternative` - _ => self.walk_next(iter), - }, - WalkEvent::Leave(next) => { - if node != *next { - panic!("Expected next `Leave` event to be for `node`."); - } - break; - } - } - } - - if used_else { - self.handle_node_leave(RSyntaxKind::R_ELSE_CLAUSE); - } - } - - fn handle_if_statement_leave(&mut self) { - self.handle_node_leave(RSyntaxKind::R_IF_STATEMENT); - } - - fn handle_braced_statements_enter(&mut self, node: tree_sitter::Node, iter: &mut Preorder) { - self.handle_node_enter(RSyntaxKind::R_BRACED_STATEMENTS); - - while let Some(event) = iter.peek() { - match event { - WalkEvent::Enter(next) => match next.syntax_kind() { - RSyntaxKind::L_CURLY => { - self.walk_next(iter); - self.handle_node_enter(RSyntaxKind::R_STATEMENT_LIST); - } - RSyntaxKind::R_CURLY => { - self.handle_node_leave(RSyntaxKind::R_STATEMENT_LIST); - self.walk_next(iter); - } - RSyntaxKind::COMMENT => self.walk_next(iter), - _ => self.handle_statement(iter), - }, - WalkEvent::Leave(next) => { - if node != *next { - panic!("Expected next `Leave` event to be for `node`."); - } - break; - } - } - } - } - - fn handle_braced_statements_leave(&mut self) { - self.handle_node_leave(RSyntaxKind::R_BRACED_STATEMENTS); - } - - /// tree-sitter-r hides `;`, so the optional `;` slot of the statement is - /// never filled, and we never produce an `R_EMPTY_STATEMENT` - fn handle_statement(&mut self, iter: &mut Preorder) { - self.handle_node_enter(RSyntaxKind::R_EXPRESSION_STATEMENT); - self.walk_next(iter); - self.handle_node_leave(RSyntaxKind::R_EXPRESSION_STATEMENT); - } - - fn handle_call_like_enter( - &mut self, - kind: RSyntaxKind, - arguments_kind: RSyntaxKind, - node: tree_sitter::Node, - iter: &mut Preorder, - ) { - self.handle_node_enter(kind); - - while let Some(event) = iter.peek() { - match event { - WalkEvent::Enter(_) => match iter.peek_field_name() { - Some("arguments") => self.handle_arguments(arguments_kind, iter), - // `"function"` field and comments - _ => self.walk_next(iter), - }, - WalkEvent::Leave(next) => { - if node != *next { - panic!("Expected next `Leave` event to be for `node`."); - } - break; - } - } - } - } - - fn handle_call_like_leave(&mut self, kind: RSyntaxKind) { - self.handle_node_leave(kind); - } - - fn handle_arguments(&mut self, kind: RSyntaxKind, iter: &mut Preorder) { - // `Enter` event for arguments - let event = iter.next().unwrap(); - let WalkEvent::Enter(node) = event else { - panic!("Expected to `Enter` arguments"); - }; - - self.handle_arguments_enter(kind, node, iter); - - // `Leave` event for arguments - let event = iter.next().unwrap(); - assert_eq!( - event, - WalkEvent::Leave(node), - "Expected to `Leave` arguments" - ); - - self.handle_arguments_leave(kind); - } - - fn handle_arguments_enter( - &mut self, - kind: RSyntaxKind, - node: tree_sitter::Node, - iter: &mut Preorder, - ) { - let (open, close) = match kind { - RSyntaxKind::R_CALL_ARGUMENTS => (RSyntaxKind::L_PAREN, RSyntaxKind::R_PAREN), - RSyntaxKind::R_SUBSET_ARGUMENTS => (RSyntaxKind::L_BRACK, RSyntaxKind::R_BRACK), - RSyntaxKind::R_SUBSET2_ARGUMENTS => (RSyntaxKind::L_BRACK2, RSyntaxKind::R_BRACK2), - _ => unreachable!("Found unexpected kind '{kind:?}'."), - }; - - self.handle_node_enter(kind); - - let mut last_kind = RSyntaxKind::R_BOGUS; - - while let Some(event) = iter.peek() { - match event { - WalkEvent::Enter(next) => match next.syntax_kind() { - kind if kind == open => { - self.walk_next(iter); - self.handle_node_enter(RSyntaxKind::R_ARGUMENT_LIST); - last_kind = open; - } - kind if kind == close => { - self.handle_hole_before_close(last_kind); - self.handle_node_leave(RSyntaxKind::R_ARGUMENT_LIST); - self.walk_next(iter); - last_kind = close; - } - RSyntaxKind::COMMA => { - self.handle_hole_before_comma(last_kind, open); - self.walk_next(iter); - last_kind = RSyntaxKind::COMMA; - } - RSyntaxKind::R_ARGUMENT => { - self.walk_next(iter); - last_kind = RSyntaxKind::R_ARGUMENT; - } - RSyntaxKind::COMMENT => { - self.walk_next(iter); - // Not setting `last_kind` here! - } - kind => unreachable!("Found {kind:?} in arguments"), - }, - WalkEvent::Leave(next) => { - if node != *next { - panic!("Expected next `Leave` event to be for `node`."); - } - break; - } - } - } - } - - fn handle_arguments_leave(&mut self, kind: RSyntaxKind) { - self.handle_node_leave(kind); - } - - fn handle_argument_enter(&mut self, node: tree_sitter::Node, iter: &mut Preorder) { - self.handle_node_enter(RSyntaxKind::R_ARGUMENT); - - // A complication with arguments is that tree-sitter-r doesn't differentiate - // between named and unnamed arguments at the node level. Named arguments - // have a `"name"` field node (identifier/string/dots/dotdoti/null) followed - // by an `=` sign. If we see that `"name"` field, we open the name clause - // node and close it after we see the `=`. - let mut seen_equal = false; - - while let Some(event) = iter.peek() { - match event { - WalkEvent::Enter(next) => match next.syntax_kind() { - RSyntaxKind::R_IDENTIFIER - | RSyntaxKind::R_STRING_VALUE - | RSyntaxKind::R_DOTS - | RSyntaxKind::R_DOT_DOT_I - | RSyntaxKind::R_NULL_EXPRESSION => { - if !seen_equal - && let Some(next_field_name) = iter.peek_field_name() - && next_field_name == "name" - { - self.handle_node_enter(RSyntaxKind::R_ARGUMENT_NAME_CLAUSE) - } - self.walk_next(iter); - } - RSyntaxKind::EQUAL => { - seen_equal = true; - self.walk_next(iter); - self.handle_node_leave(RSyntaxKind::R_ARGUMENT_NAME_CLAUSE); - } - // Comments, values, etc - _ => self.walk_next(iter), - }, - WalkEvent::Leave(next) => { - if node != *next { - panic!("Expected next `Leave` event to be for `node`."); - } - break; - } - } - } - } - - fn handle_argument_leave(&mut self) { - self.handle_node_leave(RSyntaxKind::R_ARGUMENT); - } - - /// Is there a hole before this `)`, `]`, or `]]`? - /// - /// ```r - /// fn(,) - /// - /// fn( - /// x, - /// # comment - /// - /// ) - /// ``` - fn handle_hole_before_close(&mut self, last_kind: RSyntaxKind) { - if last_kind == RSyntaxKind::COMMA { - self.handle_node_enter(RSyntaxKind::R_ARGUMENT); - self.handle_node_leave(RSyntaxKind::R_ARGUMENT); - } - } - - /// Is there a hole before this `,`? - /// - /// ```r - /// fn(,) - /// - /// fn(x, ,) - /// - /// fn( - /// x, - /// # comment - /// , - /// ) - /// ``` - fn handle_hole_before_comma(&mut self, last_kind: RSyntaxKind, open: RSyntaxKind) { - if last_kind == RSyntaxKind::COMMA || last_kind == open { - self.handle_node_enter(RSyntaxKind::R_ARGUMENT); - self.handle_node_leave(RSyntaxKind::R_ARGUMENT); - } - } -} - -struct RWalkEvents { - events: Vec>, - trivia: Vec, - - /// Whether trivia trails the last token. Set by a token, cleared by a - /// newline. - /// - /// TODO(aether): Temporary field that is removed on the swap to aether - trailing: bool, -} - -impl RWalkEvents { - fn new() -> Self { - Self { - events: Vec::new(), - trivia: Vec::new(), - trailing: false, - } - } - - fn start(&mut self, kind: RSyntaxKind) { - self.push_event(Event::Start { - kind, - forward_parent: None, - }); - } - - fn token(&mut self, kind: RSyntaxKind, end: TextSize) { - self.push_event(Event::Token { kind, end }); - self.trailing = true; - } - - fn finish(&mut self) { - self.push_event(Event::Finish); - } - - fn comment(&mut self, range: TextRange) { - // Comments are "single line" even if they are consecutive - self.push_trivia(TriviaPieceKind::SingleLineComment, range); - } - - fn push_event(&mut self, event: Event) { - self.events.push(event); - } - - fn push_trivia(&mut self, kind: TriviaPieceKind, range: TextRange) { - if kind.is_newline() { - self.trailing = false; - } - self.trivia.push(Trivia::new(kind, range, self.trailing)); - } - - fn drain(self) -> (Vec>, Vec) { - (self.events, self.trivia) - } - - /// Given: - /// - A slice of `text` starting at byte `start` - /// - Which only contains whitespace or newlines - /// - And represents a "gap" between two tokens - /// - /// Derive the implied stream of trivia that exists in that gap - /// - /// TODO(aether): Trivia trails the token before it until a newline is seen, - /// and leads the token after it from the newline onward. Before the first - /// token of the file, everything leads. This matches Biome's parsers, and the - /// new aether parser. - /// - /// TODO(semicolon): tree-sitter-r does not emit semicolons in the parse tree, so they - /// technically also appear here in `text`. We hackily patch over this for now by - /// consuming semicolons as whitespace, which ends up getting ignored by the - /// formatter. It would be nice to have official semicolon handling, and then in the - /// formatter we'd just use `format_removed()` to officially remove semicolons. - /// - /// SAFETY: `last_end <= next_start` - /// SAFETY: `last_end` and `next_start` must define a range within `text` - fn derive_trivia(&mut self, text: &str, mut start: TextSize) { - let mut iter = text.as_bytes().iter().peekable(); - let mut end = start; - - while let Some(byte) = iter.next() { - end += TextSize::from(1); - - if Self::is_whitespace(*byte) { - // Finish out stream of whitespace - while iter.next_if(|byte| Self::is_whitespace(**byte)).is_some() { - end += TextSize::from(1); - } - let range = TextRange::new(start, end); - self.push_trivia(TriviaPieceKind::Whitespace, range); - start = end; - continue; - } - - // A lone `\r` not attached to an `\n` should not happen in a - // well-formed file (unless inside a string token), so we just - // treat it as a `\r\n` line ending. - if let b'\r' = byte { - match iter.next_if(|byte| **byte == b'\n') { - Some(_) => { - // Finish out `\r\n` - end += TextSize::from(1); - let range = TextRange::new(start, end); - self.push_trivia(TriviaPieceKind::Newline, range); - start = end; - } - None => { - // Finish out `\r` - let range = TextRange::new(start, end); - self.push_trivia(TriviaPieceKind::Newline, range); - start = end; - } - } - continue; - } - - if let b'\n' = byte { - // Finish out `\n` - let range = TextRange::new(start, end); - self.push_trivia(TriviaPieceKind::Newline, range); - start = end; - continue; - } - - unreachable!("Detected non trivia character!"); - } - } - - fn is_whitespace(byte: u8) -> bool { - // `WHS` maps newlines as "whitespace" but we handle that specially - match biome_unicode_table::lookup_byte(byte) { - Dispatch::WHS => byte != b'\r' && byte != b'\n', - // TODO(semicolon): Mapping semicolons as whitespace until we handle them officially - Dispatch::SEM => true, - _ => false, - } - } + RParse::new(green, diagnostics) } #[cfg(test)] mod tests { use super::*; + use air_r_syntax::RSyntaxKind; + use biome_diagnostics::PrintDescription; + use biome_rowan::AstNodeList; - enum Pos { - Leading, - Trailing, - } - - fn trivia(text: &str) -> Vec { - let (_events, trivia, _errors) = parse_text(text, RParserOptions::default()); - trivia - } - - fn ws(start: u32, end: u32, position: Pos) -> Trivia { - Trivia::new( - TriviaPieceKind::Whitespace, - TextRange::new(TextSize::from(start), TextSize::from(end)), - matches!(position, Pos::Trailing), - ) - } - - fn nl(start: u32, end: u32) -> Trivia { - Trivia::new( - TriviaPieceKind::Newline, - TextRange::new(TextSize::from(start), TextSize::from(end)), - false, - ) - } - - fn cmt(start: u32, end: u32, position: Pos) -> Trivia { - Trivia::new( - TriviaPieceKind::SingleLineComment, - TextRange::new(TextSize::from(start), TextSize::from(end)), - matches!(position, Pos::Trailing), - ) - } - - // TODO: It would be great if `biome_parser::token_source::Trivia` - // implemented `PartialEq`, maybe we should ask for that. - fn assert_eq_trivia(lhs: Vec, rhs: Vec) { - assert_eq!(lhs.len(), rhs.len()); - - for (i, (lhs, rhs)) in lhs.iter().zip(rhs.iter()).enumerate() { - let message = format!("In event {i} with:\nlhs {lhs:?}\nrhs {rhs:?}"); - assert_eq!(lhs.kind(), rhs.kind(), "{message}"); - assert_eq!(lhs.text_range(), rhs.text_range(), "{message}"); - assert_eq!(lhs.trailing(), rhs.trailing(), "{message}"); - } - } - - // TODO: It would be great if `biome_parser::token_source::Trivia` - // implemented `PartialEq`, maybe we should ask for that. - fn assert_eq_events(lhs: Vec>, rhs: Vec>) { - assert_eq!(lhs.len(), rhs.len(), "With:\nlhs {lhs:?}\nrhs {rhs:?}"); - - for (i, (lhs, rhs)) in lhs.iter().zip(rhs.iter()).enumerate() { - let message = format!("In event {i} with:\nlhs {lhs:?}\nrhs {rhs:?}"); - - match lhs { - Event::Start { - kind: lhs_kind, - forward_parent: lhs_forward_parent, - } => match rhs { - Event::Start { - kind: rhs_kind, - forward_parent: rhs_forward_parent, - } => { - assert_eq!(lhs_kind, rhs_kind, "{message}"); - assert_eq!(lhs_forward_parent, rhs_forward_parent, "{message}"); - } - Event::Finish => panic!("{message}"), - Event::Token { .. } => panic!("{message}"), - }, - Event::Token { - kind: lhs_kind, - end: lhs_end, - } => match rhs { - Event::Token { - kind: rhs_kind, - end: rhs_end, - } => { - assert_eq!(lhs_kind, rhs_kind, "{message}"); - assert_eq!(lhs_end, rhs_end, "{message}"); - } - Event::Start { .. } => panic!("{message}"), - Event::Finish => panic!("{message}"), - }, - Event::Finish => match rhs { - Event::Finish => (), - Event::Start { .. } => panic!("{message}"), - Event::Token { .. } => panic!("{message}"), - }, - } - } - } - - #[test] - fn test_parse_trivia_smoke_test() { - assert_eq_trivia( - trivia("1 + 1"), - vec![ws(1, 2, Pos::Trailing), ws(3, 4, Pos::Trailing)], - ); - } - - #[test] - fn test_parse_trivia_tab_test() { - assert_eq_trivia( - trivia("1\t+\t\n\t1"), - vec![ - ws(1, 2, Pos::Trailing), - ws(3, 4, Pos::Trailing), - nl(4, 5), - ws(5, 6, Pos::Leading), - ], - ); - } - - #[test] - fn test_parse_trivia_trailing_test() { - assert_eq_trivia( - trivia("1 + \n1"), - vec![ws(1, 2, Pos::Trailing), ws(3, 4, Pos::Trailing), nl(4, 5)], - ); - } - - #[test] - fn test_parse_trivia_trailing_trivia_test() { - // Trivia after the newline leads the `EOF` token that `TreeSink` adds - assert_eq_trivia( - trivia("1 \n "), - vec![ws(1, 3, Pos::Trailing), nl(3, 4), ws(4, 5, Pos::Leading)], - ); - } - - #[test] - fn test_parse_trivia_trailing_crlf_test() { - assert_eq_trivia( - trivia("1 + \r\n1"), - vec![ws(1, 2, Pos::Trailing), ws(3, 4, Pos::Trailing), nl(4, 6)], - ); - } - - #[test] - fn test_parse_trivia_before_first_token() { - assert_eq_trivia(trivia(" \n1"), vec![ws(0, 2, Pos::Leading), nl(2, 3)]); - } - + /// Air's LSP currently needs this #[test] - fn test_parse_trivia_comment_test() { - assert_eq_trivia( - trivia("1 #"), - vec![ws(1, 2, Pos::Trailing), cmt(2, 3, Pos::Trailing)], - ); + fn parse_is_send() { + fn assert_send() {} + assert_send::(); } #[test] - fn test_parse_trivia_comment_crlf_test() { - // Trailing comment: `1 # hi\r\n` (r-lib/tree-sitter-r#184) - assert_eq_trivia( - trivia("1 # hi\r\n"), - vec![ws(1, 2, Pos::Trailing), cmt(2, 6, Pos::Trailing), nl(6, 8)], - ); + fn parse_empty() { + let parse = parse("", RParserOptions::default()); + assert!(parse.diagnostics().is_empty()); + assert_eq!(parse.syntax().to_string(), ""); - // Own-line comment: `# hi\r\n1` - assert_eq_trivia(trivia("# hi\r\n1"), vec![cmt(0, 4, Pos::Leading), nl(4, 6)]); + let root = parse.tree(); + assert!(root.statements().is_empty()); + assert_eq!(root.eof_token().unwrap().kind(), RSyntaxKind::EOF); } + /// Trivia with no token to trail lands on `EOF` #[test] - fn test_parse_trivia_crlf_leading_and_trailing_comments() { - assert_eq_trivia( - trivia("# Leading\r\n1 + 1 # Trailing\r\n# Leading\r\n"), - vec![ - cmt(0, 9, Pos::Leading), - nl(9, 11), - ws(12, 13, Pos::Trailing), - ws(14, 15, Pos::Trailing), - ws(16, 17, Pos::Trailing), - cmt(17, 27, Pos::Trailing), - nl(27, 29), - cmt(29, 38, Pos::Leading), - nl(38, 40), - ], - ); - } + fn parse_trivia_only() { + let source = " \n# hi\n"; + let parse = parse(source, RParserOptions::default()); + assert!(parse.diagnostics().is_empty()); + assert_eq!(parse.syntax().to_string(), source); - #[test] - fn test_parse_trivia_comment_nothing_else_test() { - assert_eq_trivia(trivia("#"), vec![cmt(0, 1, Pos::Leading)]); + let eof = parse.tree().eof_token().unwrap(); + assert_eq!(eof.leading_trivia().text(), source); + assert_eq!(eof.trailing_trivia().text(), ""); } + /// A BOM is a token of `RRoot`, not trivia #[test] - fn test_parse_trivia_comment_end_of_document_test() { - assert_eq_trivia(trivia("1\n#"), vec![nl(1, 2), cmt(2, 3, Pos::Leading)]); - } + fn parse_bom() { + let source = "\u{feff}"; + let with_bom = parse(source, RParserOptions::default()); + assert!(with_bom.diagnostics().is_empty()); + assert_eq!(with_bom.syntax().to_string(), source); + assert!(with_bom.tree().bom_token().is_some()); - #[test] - fn test_parse_trivia_whitespace_between_comments_test() { - let text = " -1 # -# -2 -" - .trim(); - assert_eq_trivia( - trivia(text), - vec![ - ws(1, 2, Pos::Trailing), - cmt(2, 3, Pos::Trailing), - nl(3, 4), - cmt(4, 5, Pos::Leading), - nl(5, 6), - ], - ); + let without_bom = parse("", RParserOptions::default()); + assert!(without_bom.tree().bom_token().is_none()); } + /// The lexer's diagnostics reach `RParse` through the tree sink #[test] - fn test_parse_trivia_comment_beginning_of_document_test() { - assert_eq_trivia(trivia("#\n1"), vec![cmt(0, 1, Pos::Leading), nl(1, 2)]); - } + fn parse_reports_lexer_diagnostics() { + let source = "`a"; + let parse = parse(source, RParserOptions::default()); + assert_eq!(parse.syntax().to_string(), source); - #[test] - fn test_parse_trivia_comment_beginning_of_document_with_whitespace_test() { - assert_eq_trivia( - trivia(" \n \n#"), - vec![ - ws(0, 1, Pos::Leading), - nl(1, 2), - ws(2, 3, Pos::Leading), - nl(3, 4), - cmt(4, 5, Pos::Leading), - ], + assert!(parse.has_errors()); + assert_eq!(parse.diagnostics().len(), 1); + assert_eq!( + PrintDescription(&parse.diagnostics()[0]).to_string(), + "Unterminated backtick-quoted name." ); } - - #[test] - fn test_parse_smoke_test() { - let (events, trivia, _errors) = parse_text("1+1", RParserOptions::default()); - - let expect = vec![ - Event::Start { - kind: RSyntaxKind::R_ROOT, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_STATEMENT_LIST, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_EXPRESSION_STATEMENT, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_BINARY_EXPRESSION, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_DOUBLE_VALUE, - forward_parent: None, - }, - Event::Token { - kind: RSyntaxKind::R_DOUBLE_LITERAL, - end: TextSize::from(1), - }, - Event::Finish, - Event::Token { - kind: RSyntaxKind::PLUS, - end: TextSize::from(2), - }, - Event::Start { - kind: RSyntaxKind::R_DOUBLE_VALUE, - forward_parent: None, - }, - Event::Token { - kind: RSyntaxKind::R_DOUBLE_LITERAL, - end: TextSize::from(3), - }, - Event::Finish, - Event::Finish, - Event::Finish, - Event::Finish, - Event::Finish, - ]; - - assert_eq_events(events, expect); - assert!(trivia.is_empty()); - } - - #[test] - fn test_parse_function_definition() { - let (events, trivia, _errors) = parse_text("function() 1", RParserOptions::default()); - - let expect = vec![ - Event::Start { - kind: RSyntaxKind::R_ROOT, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_STATEMENT_LIST, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_EXPRESSION_STATEMENT, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_FUNCTION_DEFINITION, - forward_parent: None, - }, - Event::Token { - kind: RSyntaxKind::FUNCTION_KW, - end: TextSize::from(8), - }, - Event::Start { - kind: RSyntaxKind::R_PARAMETERS, - forward_parent: None, - }, - Event::Token { - kind: RSyntaxKind::L_PAREN, - end: TextSize::from(9), - }, - Event::Start { - kind: RSyntaxKind::R_PARAMETER_LIST, - forward_parent: None, - }, - Event::Finish, // R_PARAMETER_LIST - Event::Token { - kind: RSyntaxKind::R_PAREN, - end: TextSize::from(10), - }, - Event::Finish, // R_PARAMETERS - Event::Start { - kind: RSyntaxKind::R_DOUBLE_VALUE, - forward_parent: None, - }, - Event::Token { - kind: RSyntaxKind::R_DOUBLE_LITERAL, - end: TextSize::from(12), - }, - Event::Finish, // R_DOUBLE_VALUE - Event::Finish, // R_FUNCTION_DEFINITION - Event::Finish, // R_EXPRESSION_STATEMENT - Event::Finish, // R_STATEMENT_LIST - Event::Finish, // R_ROOT - ]; - assert_eq_events(events, expect); - - let expect = vec![ws(10, 11, Pos::Trailing)]; - assert_eq_trivia(trivia, expect); - } - - #[test] - fn test_parse_call() { - let (events, trivia, _errors) = parse_text("fn()", RParserOptions::default()); - - let expect = vec![ - Event::Start { - kind: RSyntaxKind::R_ROOT, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_STATEMENT_LIST, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_EXPRESSION_STATEMENT, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_CALL, - forward_parent: None, - }, - Event::Start { - kind: RSyntaxKind::R_IDENTIFIER, - forward_parent: None, - }, - Event::Token { - kind: RSyntaxKind::IDENT, - end: TextSize::from(2), - }, - Event::Finish, // R_IDENTIFIER - Event::Start { - kind: RSyntaxKind::R_CALL_ARGUMENTS, - forward_parent: None, - }, - Event::Token { - kind: RSyntaxKind::L_PAREN, - end: TextSize::from(3), - }, - Event::Start { - kind: RSyntaxKind::R_ARGUMENT_LIST, - forward_parent: None, - }, - Event::Finish, // R_ARGUMENT_LIST - Event::Token { - kind: RSyntaxKind::R_PAREN, - end: TextSize::from(4), - }, - Event::Finish, // R_CALL_ARGUMENTS - Event::Finish, // R_CALL - Event::Finish, // R_EXPRESSION_STATEMENT - Event::Finish, // R_STATEMENT_LIST - Event::Finish, // R_ROOT - ]; - assert_eq_events(events, expect); - - let expect = vec![]; - assert_eq_trivia(trivia, expect); - } } diff --git a/crates/air_r_parser/src/parser.rs b/crates/air_r_parser/src/parser.rs new file mode 100644 index 00000000..5251b226 --- /dev/null +++ b/crates/air_r_parser/src/parser.rs @@ -0,0 +1,118 @@ +use crate::lexer::RReLexContext; +use crate::options::RParserOptions; +use crate::token_source::RTokenSource; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::T; +use biome_parser::ParserContext; +use biome_parser::diagnostic::ParseDiagnostic; +use biome_parser::diagnostic::merge_diagnostics; +use biome_parser::event::Event; +use biome_parser::prelude::*; + +pub(crate) struct RParser<'src> { + context: ParserContext, + source: RTokenSource<'src>, + options: RParserOptions, + scope: Scope, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub(crate) enum Scope { + #[default] + TopLevel, + Brace, + Parenthesis, + Bracket, + Bracket2, +} + +impl Scope { + /// A line break ends a statement at top level and inside `{`, and means + /// nothing inside `(`, `[`, or `[[` + pub(crate) fn newlines_are_significant(self) -> bool { + match self { + Self::TopLevel | Self::Brace => true, + Self::Parenthesis | Self::Bracket | Self::Bracket2 => false, + } + } +} + +impl<'src> RParser<'src> { + pub(crate) fn new(source: &'src str, options: RParserOptions) -> Self { + Self { + context: ParserContext::default(), + source: RTokenSource::from_str(source), + options, + scope: Scope::default(), + } + } + + #[expect(dead_code, reason = "`RParserOptions` is still empty")] + pub(crate) fn options(&self) -> &RParserOptions { + &self.options + } + + /// Runs `f` with `scope` in effect, restoring the previous value after + pub(crate) fn with_scope(&mut self, scope: Scope, f: F) -> R + where + F: FnOnce(&mut Self) -> R, + { + let saved = std::mem::replace(&mut self.scope, scope); + let result = f(self); + self.scope = saved; + result + } + + pub(crate) fn scope(&self) -> Scope { + self.scope + } + + pub(crate) fn re_lex(&mut self, context: RReLexContext) -> RSyntaxKind { + self.source_mut().re_lex(context) + } + + pub(crate) fn finish(self) -> (Vec>, Vec, Vec) { + let (trivia, lexer_diagnostics) = self.source.finish(); + let (events, parse_diagnostics) = self.context.finish(); + let diagnostics = merge_diagnostics(lexer_diagnostics, parse_diagnostics); + (events, trivia, diagnostics) + } +} + +impl<'src> Parser for RParser<'src> { + type Kind = RSyntaxKind; + type Source = RTokenSource<'src>; + + fn context(&self) -> &ParserContext { + &self.context + } + + fn context_mut(&mut self) -> &mut ParserContext { + &mut self.context + } + + fn source(&self) -> &Self::Source { + &self.source + } + + fn source_mut(&mut self) -> &mut Self::Source { + &mut self.source + } + + /// Overridden so that every bump checks for a potential `]]` re-lex + fn do_bump(&mut self, kind: Self::Kind) { + let end = self.cur_range().end(); + self.context_mut().push_token(kind, end); + + if self.context().is_skipping() { + self.source_mut().skip_as_trivia(); + } else { + self.source_mut().bump(); + } + + // Joins an adjacent `] ]` into the `]]` the lexer never emits + if self.scope == Scope::Bracket2 && self.cur() == T![']'] { + self.re_lex(RReLexContext::JoinBrackets); + } + } +} diff --git a/crates/air_r_parser/src/syntax.rs b/crates/air_r_parser/src/syntax.rs new file mode 100644 index 00000000..fa45bcdb --- /dev/null +++ b/crates/air_r_parser/src/syntax.rs @@ -0,0 +1,20 @@ +mod associativity; +mod binary; +mod binding_power; +mod body; +mod braced_statements; +mod call; +mod expr; +mod extract; +mod function; +mod if_statement; +mod literal; +mod loop_statements; +mod namespace; +mod parenthesized_expression; +mod root; +mod selector; +mod statement_list; +mod unary; + +pub(crate) use crate::syntax::root::parse_root; diff --git a/crates/air_r_parser/src/syntax/associativity.rs b/crates/air_r_parser/src/syntax/associativity.rs new file mode 100644 index 00000000..00517adb --- /dev/null +++ b/crates/air_r_parser/src/syntax/associativity.rs @@ -0,0 +1,22 @@ +/// Binary operator associativity, taken from `gram.y` +#[derive(Debug, Clone, Copy)] +pub(crate) enum Associativity { + Left, + Right, +} + +impl Associativity { + pub(crate) const BINARY_HELP: Self = Self::Left; + pub(crate) const BINARY_LEFT_ASSIGN: Self = Self::Right; + pub(crate) const BINARY_EQUALS_ASSIGN: Self = Self::Right; + pub(crate) const BINARY_RIGHT_ASSIGN: Self = Self::Left; + pub(crate) const BINARY_TILDE: Self = Self::Left; + pub(crate) const BINARY_OR: Self = Self::Left; + pub(crate) const BINARY_AND: Self = Self::Left; + pub(crate) const BINARY_COMPARISON: Self = Self::Left; + pub(crate) const BINARY_PLUS_MINUS: Self = Self::Left; + pub(crate) const BINARY_MULTIPLY_DIVIDE: Self = Self::Left; + pub(crate) const BINARY_SPECIAL_OR_PIPE: Self = Self::Left; + pub(crate) const BINARY_COLON: Self = Self::Left; + pub(crate) const BINARY_EXPONENTIATE: Self = Self::Right; +} diff --git a/crates/air_r_parser/src/syntax/binary.rs b/crates/air_r_parser/src/syntax/binary.rs new file mode 100644 index 00000000..39067d38 --- /dev/null +++ b/crates/air_r_parser/src/syntax/binary.rs @@ -0,0 +1,27 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::syntax::associativity::Associativity; +use crate::syntax::binding_power::BindingPower; +use crate::syntax::expr::parse_expression_with_binding_power; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::R_BINARY_EXPRESSION; +use biome_parser::CompletedMarker; +use biome_parser::Parser; + +pub(crate) fn parse_binary_expression( + p: &mut RParser, + lhs: CompletedMarker, + kind: RSyntaxKind, + binding_power: BindingPower, + associativity: Associativity, +) -> CompletedMarker { + let binding_power = match associativity { + Associativity::Left => binding_power.increment(), + Associativity::Right => binding_power, + }; + let m = lhs.precede(p); + p.bump(kind); + parse_expression_with_binding_power(p, binding_power) + .or_add_diagnostic(p, diagnostics::expected_expression); + m.complete(p, R_BINARY_EXPRESSION) +} diff --git a/crates/air_r_parser/src/syntax/binding_power.rs b/crates/air_r_parser/src/syntax/binding_power.rs new file mode 100644 index 00000000..c84aa4f9 --- /dev/null +++ b/crates/air_r_parser/src/syntax/binding_power.rs @@ -0,0 +1,34 @@ +/// How tightly an operator binds. Higher wins! +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct BindingPower(u8); + +impl BindingPower { + pub(crate) const LOWEST: Self = Self(0); + + // Taken from R's `gram.y` + pub(crate) const BINARY_HELP: Self = Self(1); + pub(crate) const UNARY_HELP: Self = Self(1); + pub(crate) const FUNCTION_OR_LOOP_OR_IF: Self = Self(2); + pub(crate) const BINARY_LEFT_ASSIGN: Self = Self(3); + pub(crate) const BINARY_EQUALS_ASSIGN: Self = Self(4); + pub(crate) const BINARY_RIGHT_ASSIGN: Self = Self(5); + pub(crate) const BINARY_TILDE: Self = Self(6); + pub(crate) const UNARY_TILDE: Self = Self(6); + pub(crate) const BINARY_OR: Self = Self(7); + pub(crate) const BINARY_AND: Self = Self(8); + pub(crate) const UNARY_NOT: Self = Self(9); + pub(crate) const BINARY_COMPARISON: Self = Self(10); + pub(crate) const BINARY_PLUS_MINUS: Self = Self(11); + pub(crate) const BINARY_MULTIPLY_DIVIDE: Self = Self(12); + pub(crate) const BINARY_SPECIAL_OR_PIPE: Self = Self(13); + pub(crate) const BINARY_COLON: Self = Self(14); + pub(crate) const UNARY_PLUS_MINUS: Self = Self(15); + pub(crate) const BINARY_EXPONENTIATE: Self = Self(16); + + pub(crate) const HIGHEST: Self = Self(17); + + /// Used by left associative constructs + pub(crate) fn increment(self) -> Self { + Self(self.0 + 1) + } +} diff --git a/crates/air_r_parser/src/syntax/body.rs b/crates/air_r_parser/src/syntax/body.rs new file mode 100644 index 00000000..26076bf1 --- /dev/null +++ b/crates/air_r_parser/src/syntax/body.rs @@ -0,0 +1,15 @@ +use crate::parser::RParser; +use crate::syntax::binding_power::BindingPower; +use crate::syntax::expr::parse_expression_with_binding_power; +use biome_parser::parsed_syntax::ParsedSyntax; + +/// Parse a function, loop, or if body +/// +/// Generally treated like a normal expression, but R's precedence table puts +/// `?` at a lower binding power than a function, loop, or if body, so `function() a ? b` +/// needs to parse as `(function () a) ? b`. +pub(crate) fn parse_body_expression(p: &mut RParser) -> ParsedSyntax { + // Treated as left associative + let binding_power = BindingPower::FUNCTION_OR_LOOP_OR_IF.increment(); + parse_expression_with_binding_power(p, binding_power) +} diff --git a/crates/air_r_parser/src/syntax/braced_statements.rs b/crates/air_r_parser/src/syntax/braced_statements.rs new file mode 100644 index 00000000..fcb48229 --- /dev/null +++ b/crates/air_r_parser/src/syntax/braced_statements.rs @@ -0,0 +1,17 @@ +use crate::parser::RParser; +use crate::parser::Scope; +use crate::syntax::statement_list::parse_statement_list; +use air_r_syntax::RSyntaxKind::R_BRACED_STATEMENTS; +use air_r_syntax::T; +use biome_parser::CompletedMarker; +use biome_parser::Parser; + +pub(crate) fn parse_braced_statements(p: &mut RParser) -> CompletedMarker { + let m = p.start(); + p.with_scope(Scope::Brace, |p| { + p.bump(T!['{']); + parse_statement_list(p, T!['}']); + }); + p.expect(T!['}']); + m.complete(p, R_BRACED_STATEMENTS) +} diff --git a/crates/air_r_parser/src/syntax/call.rs b/crates/air_r_parser/src/syntax/call.rs new file mode 100644 index 00000000..eef38fb0 --- /dev/null +++ b/crates/air_r_parser/src/syntax/call.rs @@ -0,0 +1,199 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::parser::Scope; +use crate::syntax::expr::parse_expression; +use crate::syntax::literal::parse_literal_expression; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::DOTDOTI; +use air_r_syntax::RSyntaxKind::DOTS; +use air_r_syntax::RSyntaxKind::IDENT; +use air_r_syntax::RSyntaxKind::R_ARGUMENT; +use air_r_syntax::RSyntaxKind::R_ARGUMENT_LIST; +use air_r_syntax::RSyntaxKind::R_ARGUMENT_NAME_CLAUSE; +use air_r_syntax::RSyntaxKind::R_BOGUS_EXPRESSION; +use air_r_syntax::RSyntaxKind::R_CALL; +use air_r_syntax::RSyntaxKind::R_CALL_ARGUMENTS; +use air_r_syntax::RSyntaxKind::R_DOT_DOT_I; +use air_r_syntax::RSyntaxKind::R_DOTS; +use air_r_syntax::RSyntaxKind::R_DOUBLE_QUOTED_STRING_LITERAL; +use air_r_syntax::RSyntaxKind::R_IDENTIFIER; +use air_r_syntax::RSyntaxKind::R_NULL_EXPRESSION; +use air_r_syntax::RSyntaxKind::R_RAW_STRING_LITERAL; +use air_r_syntax::RSyntaxKind::R_SINGLE_QUOTED_STRING_LITERAL; +use air_r_syntax::RSyntaxKind::R_STRING_VALUE; +use air_r_syntax::RSyntaxKind::R_SUBSET; +use air_r_syntax::RSyntaxKind::R_SUBSET_ARGUMENTS; +use air_r_syntax::RSyntaxKind::R_SUBSET2; +use air_r_syntax::RSyntaxKind::R_SUBSET2_ARGUMENTS; +use air_r_syntax::T; +use biome_parser::CompletedMarker; +use biome_parser::Parser; +use biome_parser::ParserProgress; +use biome_parser::TokenSet; +use biome_parser::parse_recovery::ParseRecoveryTokenSet; +use biome_parser::token_set; + +pub(crate) fn parse_call_expression(p: &mut RParser, lhs: CompletedMarker) -> CompletedMarker { + let m = lhs.precede(p); + + let arguments = p.start(); + p.with_scope(Scope::Parenthesis, |p| { + p.bump(T!['(']); + parse_argument_list(p); + }); + p.expect(T![')']); + arguments.complete(p, R_CALL_ARGUMENTS); + + m.complete(p, R_CALL) +} + +pub(crate) fn parse_subset_expression(p: &mut RParser, lhs: CompletedMarker) -> CompletedMarker { + let m = lhs.precede(p); + + let arguments = p.start(); + p.with_scope(Scope::Bracket, |p| { + p.bump(T!['[']); + parse_argument_list(p); + }); + p.expect(T![']']); + arguments.complete(p, R_SUBSET_ARGUMENTS); + + m.complete(p, R_SUBSET) +} + +pub(crate) fn parse_subset2_expression(p: &mut RParser, lhs: CompletedMarker) -> CompletedMarker { + let m = lhs.precede(p); + + let arguments = p.start(); + p.with_scope(Scope::Bracket2, |p| { + // Purposefully bump inside `with_scope()`. Bumping lexes the next + // token after this one, and the context for how two back to back `]` + // `]` are lexed depends on whether we are in a Bracket2 scope or not. + // If we bump before `with_scope()` then an empty `x[[]]` parses + // incorrectly! + p.bump(T!["[["]); + parse_argument_list(p); + }); + // Similarly, we `expect()` (bump + assert) outside the scope so that a + // potential future `]` can't be affected by a now closed Bracket2 scope + p.expect(T!["]]"]); + arguments.complete(p, R_SUBSET2_ARGUMENTS); + + m.complete(p, R_SUBSET2) +} + +/// What ends an argument list +/// +/// Any closing delimiter, not just the one the caller opened. We don't consume +/// it, we return from here. The caller's `expect()` on the closing delimiter +/// reports a diagnostic, which still doesn't consume, and the delimiter +/// survives (ideally to be consumed by its actual owner). +const ARGUMENT_LIST_ENDS: TokenSet = + token_set![T![EOF], T![')'], T![']'], T!["]]"], T!['}']]; + +/// Where an argument may stop: at the `,` that starts the next one, or at an end +const ARGUMENT_LIST_BOUNDARIES: TokenSet = ARGUMENT_LIST_ENDS.union(token_set![T![,]]); + +fn parse_argument_list(p: &mut RParser) { + let argument_list = p.start(); + let mut progress = ParserProgress::default(); + let mut first = true; + + while !p.at_ts(ARGUMENT_LIST_ENDS) { + if first { + first = false; + } else { + // Either a `,` that introduces the next argument, or a missing one + // that we report without consuming and let the next argument start + // where we already are + p.expect(T![,]); + } + + progress.assert_progressing(p); + parse_argument(p); + } + + argument_list.complete(p, R_ARGUMENT_LIST); +} + +/// One argument: `[name =] [value]`, both halves optional +/// +/// An empty argument opens and immediately closes its marker, pushing no +/// children. That is the technique `r.ungram` documents, and it is what makes +/// `f(,,a,,b,,)` representable. +fn parse_argument(p: &mut RParser) { + let m = p.start(); + + // Optional name clause + parse_argument_name_clause(p); + + // Optional value + parse_argument_value(p); + + m.complete(p, R_ARGUMENT); +} + +/// Value can be missing as long as the next token is a boundary, like +/// `f(a,)` where the next token is `)` or `f(a,,)` where the next +/// token is `,`, but not with `f(* x)` where `* x` simply isn't an expression. +/// That should be a parse error! +fn parse_argument_value(p: &mut RParser) { + if p.at_ts(ARGUMENT_LIST_BOUNDARIES) { + return; + } + + parse_expression(p) + .or_recover_with_token_set( + p, + &ParseRecoveryTokenSet::new(R_BOGUS_EXPRESSION, ARGUMENT_LIST_BOUNDARIES), + diagnostics::expected_expression, + ) + .ok(); +} + +/// `name =`, where the `name` is an identifier, a string, `...`, `..i`, or `NULL` +/// +/// `foo` and `foo =` start the same way, so one token of lookahead is what tells +/// a name from the head of a value +/// +/// `NULL` is accepted here and nowhere else, and has been since the +/// beginning of R (r-lib/tree-sitter-r#164). +fn parse_argument_name_clause(p: &mut RParser) { + match p.cur() { + IDENT if p.nth_at(1, T![=]) => { + let clause = p.start(); + parse_literal_expression(p, IDENT, R_IDENTIFIER); + p.bump(T![=]); + clause.complete(p, R_ARGUMENT_NAME_CLAUSE); + } + DOTS if p.nth_at(1, T![=]) => { + let clause = p.start(); + parse_literal_expression(p, DOTS, R_DOTS); + p.bump(T![=]); + clause.complete(p, R_ARGUMENT_NAME_CLAUSE); + } + DOTDOTI if p.nth_at(1, T![=]) => { + let clause = p.start(); + parse_literal_expression(p, DOTDOTI, R_DOT_DOT_I); + p.bump(T![=]); + clause.complete(p, R_ARGUMENT_NAME_CLAUSE); + } + T![null] if p.nth_at(1, T![=]) => { + let clause = p.start(); + parse_literal_expression(p, T![null], R_NULL_EXPRESSION); + p.bump(T![=]); + clause.complete(p, R_ARGUMENT_NAME_CLAUSE); + } + kind @ (R_SINGLE_QUOTED_STRING_LITERAL + | R_DOUBLE_QUOTED_STRING_LITERAL + | R_RAW_STRING_LITERAL) + if p.nth_at(1, T![=]) => + { + let clause = p.start(); + parse_literal_expression(p, kind, R_STRING_VALUE); + p.bump(T![=]); + clause.complete(p, R_ARGUMENT_NAME_CLAUSE); + } + _ => {} + } +} diff --git a/crates/air_r_parser/src/syntax/expr.rs b/crates/air_r_parser/src/syntax/expr.rs new file mode 100644 index 00000000..0a563ac7 --- /dev/null +++ b/crates/air_r_parser/src/syntax/expr.rs @@ -0,0 +1,239 @@ +//! Pratt parser core +//! +//! https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html +//! https://kevinushey.github.io/blog/2016/02/12/top-down-operator-precedence-parsing-with-r/ + +use crate::parser::RParser; +use crate::syntax::associativity::Associativity; +use crate::syntax::binary::parse_binary_expression; +use crate::syntax::binding_power::BindingPower; +use crate::syntax::braced_statements::parse_braced_statements; +use crate::syntax::call::parse_call_expression; +use crate::syntax::call::parse_subset_expression; +use crate::syntax::call::parse_subset2_expression; +use crate::syntax::extract::parse_extract_expression; +use crate::syntax::function::parse_function_definition; +use crate::syntax::if_statement::parse_if_statement; +use crate::syntax::literal::parse_literal_expression; +use crate::syntax::loop_statements::parse_for_statement; +use crate::syntax::loop_statements::parse_repeat_statement; +use crate::syntax::loop_statements::parse_while_statement; +use crate::syntax::namespace::parse_namespace_expression; +use crate::syntax::parenthesized_expression::parse_parenthesized_expression; +use crate::syntax::unary::parse_unary_expression; +use air_r_syntax::RSyntaxKind::*; +use air_r_syntax::T; +use biome_parser::Parser; +use biome_parser::ParserProgress; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::Absent; +use biome_parser::parsed_syntax::ParsedSyntax::Present; + +pub(crate) fn parse_expression(p: &mut RParser) -> ParsedSyntax { + parse_expression_with_binding_power(p, BindingPower::LOWEST) +} + +/// The Pratt loop +pub(crate) fn parse_expression_with_binding_power( + p: &mut RParser, + current_binding_power: BindingPower, +) -> ParsedSyntax { + debug_assert!( + current_binding_power <= BindingPower::HIGHEST, + "{current_binding_power:?} must not exceed {:?}.", + BindingPower::HIGHEST + ); + + let mut lhs = match p.cur() { + kind @ T![?] => parse_unary_expression(p, kind, BindingPower::UNARY_HELP), + kind @ T![~] => parse_unary_expression(p, kind, BindingPower::UNARY_TILDE), + kind @ T![!] => parse_unary_expression(p, kind, BindingPower::UNARY_NOT), + kind @ (T![+] | T![-]) => parse_unary_expression(p, kind, BindingPower::UNARY_PLUS_MINUS), + R_INTEGER_LITERAL => parse_literal_expression(p, R_INTEGER_LITERAL, R_INTEGER_VALUE), + R_DOUBLE_LITERAL => parse_literal_expression(p, R_DOUBLE_LITERAL, R_DOUBLE_VALUE), + R_COMPLEX_LITERAL => parse_literal_expression(p, R_COMPLEX_LITERAL, R_COMPLEX_VALUE), + IDENT => parse_literal_expression(p, IDENT, R_IDENTIFIER), + DOTS => parse_literal_expression(p, DOTS, R_DOTS), + DOTDOTI => parse_literal_expression(p, DOTDOTI, R_DOT_DOT_I), + T![next] => parse_literal_expression(p, T![next], R_NEXT_EXPRESSION), + T![break] => parse_literal_expression(p, T![break], R_BREAK_EXPRESSION), + T![true] => parse_literal_expression(p, T![true], R_TRUE_EXPRESSION), + T![false] => parse_literal_expression(p, T![false], R_FALSE_EXPRESSION), + T![null] => parse_literal_expression(p, T![null], R_NULL_EXPRESSION), + T![inf] => parse_literal_expression(p, T![inf], R_INF_EXPRESSION), + T![nan] => parse_literal_expression(p, T![nan], R_NAN_EXPRESSION), + kind @ (T![na_logical] + | T![na_integer] + | T![na_double] + | T![na_complex] + | T![na_character]) => parse_literal_expression(p, kind, R_NA_EXPRESSION), + // Every ERROR_TOKEN already carries a lexer diagnostic, from + // `unexpected_character()`, `unterminated_string()`, or + // `unterminated_backtick_identifier()` + ERROR_TOKEN => parse_literal_expression(p, ERROR_TOKEN, R_BOGUS_EXPRESSION), + kind @ (R_SINGLE_QUOTED_STRING_LITERAL + | R_DOUBLE_QUOTED_STRING_LITERAL + | R_RAW_STRING_LITERAL) => parse_literal_expression(p, kind, R_STRING_VALUE), + kind @ (T![function] | T!["\\"]) => parse_function_definition(p, kind), + T![if] => parse_if_statement(p), + T![for] => parse_for_statement(p), + T![while] => parse_while_statement(p), + T![repeat] => parse_repeat_statement(p), + T!['('] => parse_parenthesized_expression(p), + T!['{'] => parse_braced_statements(p), + // Doesn't start an expression + _ => return Absent, + }; + + let mut progress = ParserProgress::default(); + + loop { + progress.assert_progressing(p); + + if p.scope().newlines_are_significant() && p.has_preceding_line_break() { + break; + } + + lhs = match p.cur() { + T!['('] => parse_call_expression(p, lhs), + T!['['] => parse_subset_expression(p, lhs), + T!["[["] => parse_subset2_expression(p, lhs), + kind @ (T![$] | T![@]) => parse_extract_expression(p, lhs, kind), + kind @ (T![::] | T![:::]) => parse_namespace_expression(p, lhs, kind), + kind @ T![?] if BindingPower::BINARY_HELP >= current_binding_power => { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_HELP, + Associativity::BINARY_HELP, + ) + } + kind @ (T![<-] | T![<<-] | T![:=]) + if BindingPower::BINARY_LEFT_ASSIGN >= current_binding_power => + { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_LEFT_ASSIGN, + Associativity::BINARY_LEFT_ASSIGN, + ) + } + kind @ T![=] if BindingPower::BINARY_EQUALS_ASSIGN >= current_binding_power => { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_EQUALS_ASSIGN, + Associativity::BINARY_EQUALS_ASSIGN, + ) + } + kind @ (T![->] | T![->>]) + if BindingPower::BINARY_RIGHT_ASSIGN >= current_binding_power => + { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_RIGHT_ASSIGN, + Associativity::BINARY_RIGHT_ASSIGN, + ) + } + kind @ T![~] if BindingPower::BINARY_TILDE >= current_binding_power => { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_TILDE, + Associativity::BINARY_TILDE, + ) + } + kind @ (T![|] | T![||]) if BindingPower::BINARY_OR >= current_binding_power => { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_OR, + Associativity::BINARY_OR, + ) + } + kind @ (T![&] | T![&&]) if BindingPower::BINARY_AND >= current_binding_power => { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_AND, + Associativity::BINARY_AND, + ) + } + kind @ (T![<] | T![<=] | T![>] | T![>=] | T![==] | T![!=]) + if BindingPower::BINARY_COMPARISON >= current_binding_power => + { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_COMPARISON, + Associativity::BINARY_COMPARISON, + ) + } + kind @ (T![+] | T![-]) if BindingPower::BINARY_PLUS_MINUS >= current_binding_power => { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_PLUS_MINUS, + Associativity::BINARY_PLUS_MINUS, + ) + } + kind @ (T![*] | T![/]) + if BindingPower::BINARY_MULTIPLY_DIVIDE >= current_binding_power => + { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_MULTIPLY_DIVIDE, + Associativity::BINARY_MULTIPLY_DIVIDE, + ) + } + kind @ (SPECIAL | T![|>]) + if BindingPower::BINARY_SPECIAL_OR_PIPE >= current_binding_power => + { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_SPECIAL_OR_PIPE, + Associativity::BINARY_SPECIAL_OR_PIPE, + ) + } + kind @ T![:] if BindingPower::BINARY_COLON >= current_binding_power => { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_COLON, + Associativity::BINARY_COLON, + ) + } + kind @ (T![^] | T![**]) + if BindingPower::BINARY_EXPONENTIATE >= current_binding_power => + { + parse_binary_expression( + p, + lhs, + kind, + BindingPower::BINARY_EXPONENTIATE, + Associativity::BINARY_EXPONENTIATE, + ) + } + // No expression to continue, or upcoming one binds looser than + // current binding power + _ => break, + }; + } + + Present(lhs) +} diff --git a/crates/air_r_parser/src/syntax/extract.rs b/crates/air_r_parser/src/syntax/extract.rs new file mode 100644 index 00000000..0631c731 --- /dev/null +++ b/crates/air_r_parser/src/syntax/extract.rs @@ -0,0 +1,21 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::syntax::selector::parse_selector; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::R_EXTRACT_EXPRESSION; +use biome_parser::CompletedMarker; +use biome_parser::Parser; + +pub(crate) fn parse_extract_expression( + p: &mut RParser, + lhs: CompletedMarker, + kind: RSyntaxKind, +) -> CompletedMarker { + let kind_range = p.cur_range(); + let m = lhs.precede(p); + p.bump(kind); + parse_selector(p).or_add_diagnostic(p, |_, range| { + diagnostics::expected_rhs_selector(kind, kind_range, range) + }); + m.complete(p, R_EXTRACT_EXPRESSION) +} diff --git a/crates/air_r_parser/src/syntax/function.rs b/crates/air_r_parser/src/syntax/function.rs new file mode 100644 index 00000000..f42c48e9 --- /dev/null +++ b/crates/air_r_parser/src/syntax/function.rs @@ -0,0 +1,125 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::parser::Scope; +use crate::syntax::body::parse_body_expression; +use crate::syntax::expr::parse_expression; +use crate::syntax::literal::parse_literal_expression; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::DOTDOTI; +use air_r_syntax::RSyntaxKind::DOTS; +use air_r_syntax::RSyntaxKind::IDENT; +use air_r_syntax::RSyntaxKind::R_BOGUS_PARAMETER_NAME; +use air_r_syntax::RSyntaxKind::R_DOT_DOT_I; +use air_r_syntax::RSyntaxKind::R_DOTS; +use air_r_syntax::RSyntaxKind::R_FUNCTION_DEFINITION; +use air_r_syntax::RSyntaxKind::R_IDENTIFIER; +use air_r_syntax::RSyntaxKind::R_PARAMETER; +use air_r_syntax::RSyntaxKind::R_PARAMETER_DEFAULT; +use air_r_syntax::RSyntaxKind::R_PARAMETER_LIST; +use air_r_syntax::RSyntaxKind::R_PARAMETERS; +use air_r_syntax::T; +use biome_parser::CompletedMarker; +use biome_parser::Parser; +use biome_parser::ParserProgress; +use biome_parser::TokenSet; +use biome_parser::parse_recovery::ParseRecoveryTokenSet; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::Absent; +use biome_parser::parsed_syntax::ParsedSyntax::Present; +use biome_parser::token_set; + +pub(crate) fn parse_function_definition(p: &mut RParser, kind: RSyntaxKind) -> CompletedMarker { + let m = p.start(); + p.bump(kind); + + if p.at(T!['(']) { + parse_parameters(p); + } else { + p.error(diagnostics::expected_parameters(p, p.cur_range())); + } + + parse_body_expression(p).or_add_diagnostic(p, diagnostics::expected_function_body); + + m.complete(p, R_FUNCTION_DEFINITION) +} + +fn parse_parameters(p: &mut RParser) { + let m = p.start(); + p.with_scope(Scope::Parenthesis, |p| { + p.bump(T!['(']); + parse_parameter_list(p); + }); + p.expect(T![')']); + m.complete(p, R_PARAMETERS); +} + +/// What ends a parameter list +/// +/// Any closing delimiter, not just the one the caller opened, for the reason +/// [crate::syntax::call]'s `ARGUMENT_LIST_ENDS` gives. +const PARAMETER_LIST_ENDS: TokenSet = + token_set![T![EOF], T![')'], T![']'], T!["]]"], T!['}']]; + +/// Where a parameter may stop: at the `=` of its own default, at the `,` that +/// starts the next one, or at an end +const PARAMETER_LIST_BOUNDARIES: TokenSet = + PARAMETER_LIST_ENDS.union(token_set![T![,], T![=]]); + +fn parse_parameter_list(p: &mut RParser) { + let parameter_list = p.start(); + let mut progress = ParserProgress::default(); + let mut first = true; + + while !p.at_ts(PARAMETER_LIST_ENDS) { + if first { + first = false; + } else { + // Either a `,` that introduces the next parameter, or a missing one + // that we report without consuming and let the next parameter start + // where we already are + p.expect(T![,]); + } + + progress.assert_progressing(p); + parse_parameter(p); + } + + parameter_list.complete(p, R_PARAMETER_LIST); +} + +/// One parameter: `name [= default]` +fn parse_parameter(p: &mut RParser) { + let m = p.start(); + + parse_parameter_name(p) + .or_recover_with_token_set( + p, + &ParseRecoveryTokenSet::new(R_BOGUS_PARAMETER_NAME, PARAMETER_LIST_BOUNDARIES), + diagnostics::expected_parameter_name, + ) + .ok(); + + if p.at(T![=]) { + parse_parameter_default(p); + } + + m.complete(p, R_PARAMETER); +} + +/// identifier | `...` | `..i` +fn parse_parameter_name(p: &mut RParser) -> ParsedSyntax { + match p.cur() { + IDENT => Present(parse_literal_expression(p, IDENT, R_IDENTIFIER)), + DOTS => Present(parse_literal_expression(p, DOTS, R_DOTS)), + DOTDOTI => Present(parse_literal_expression(p, DOTDOTI, R_DOT_DOT_I)), + _ => Absent, + } +} + +/// `= value`, where the value is required (r-lib/tree-sitter-r#161) +fn parse_parameter_default(p: &mut RParser) { + let m = p.start(); + p.bump(T![=]); + parse_expression(p).or_add_diagnostic(p, diagnostics::expected_expression); + m.complete(p, R_PARAMETER_DEFAULT); +} diff --git a/crates/air_r_parser/src/syntax/if_statement.rs b/crates/air_r_parser/src/syntax/if_statement.rs new file mode 100644 index 00000000..99af961c --- /dev/null +++ b/crates/air_r_parser/src/syntax/if_statement.rs @@ -0,0 +1,46 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::parser::Scope; +use crate::syntax::body::parse_body_expression; +use crate::syntax::expr::parse_expression; +use air_r_syntax::RSyntaxKind::R_ELSE_CLAUSE; +use air_r_syntax::RSyntaxKind::R_IF_STATEMENT; +use air_r_syntax::T; +use biome_parser::CompletedMarker; +use biome_parser::Parser; + +pub(crate) fn parse_if_statement(p: &mut RParser) -> CompletedMarker { + let m = p.start(); + p.bump(T![if]); + + p.with_scope(Scope::Parenthesis, |p| { + p.expect(T!['(']); + parse_expression(p).or_add_diagnostic(p, diagnostics::expected_condition); + }); + p.expect(T![')']); + + // The consequence and the alternative have the same limitation as a loop + // body: treated like any other expression, but `?` comes before them in the + // precedence table. In gram.y a loop body is left associative and an `if` + // body is right associative one rank up, but they shake out the same + parse_body_expression(p).or_add_diagnostic(p, diagnostics::expected_if_body); + parse_else_clause(p); + + m.complete(p, R_IF_STATEMENT) +} + +fn parse_else_clause(p: &mut RParser) { + if !p.at(T![else]) { + return; + } + + // A newline before `else` ends the `if` at top level and nowhere else + if p.scope() == Scope::TopLevel && p.has_preceding_line_break() { + return; + } + + let m = p.start(); + p.bump(T![else]); + parse_body_expression(p).or_add_diagnostic(p, diagnostics::expected_else_body); + m.complete(p, R_ELSE_CLAUSE); +} diff --git a/crates/air_r_parser/src/syntax/literal.rs b/crates/air_r_parser/src/syntax/literal.rs new file mode 100644 index 00000000..30dd231b --- /dev/null +++ b/crates/air_r_parser/src/syntax/literal.rs @@ -0,0 +1,15 @@ +use crate::parser::RParser; +use air_r_syntax::RSyntaxKind; +use biome_parser::CompletedMarker; +use biome_parser::Parser; + +/// Bumps a single token and wraps it in `kind_complete` +pub(crate) fn parse_literal_expression( + p: &mut RParser, + kind_bump: RSyntaxKind, + kind_complete: RSyntaxKind, +) -> CompletedMarker { + let m = p.start(); + p.bump(kind_bump); + m.complete(p, kind_complete) +} diff --git a/crates/air_r_parser/src/syntax/loop_statements.rs b/crates/air_r_parser/src/syntax/loop_statements.rs new file mode 100644 index 00000000..91570c52 --- /dev/null +++ b/crates/air_r_parser/src/syntax/loop_statements.rs @@ -0,0 +1,90 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::parser::Scope; +use crate::syntax::body::parse_body_expression; +use crate::syntax::expr::parse_expression; +use crate::syntax::literal::parse_literal_expression; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::DOTDOTI; +use air_r_syntax::RSyntaxKind::DOTS; +use air_r_syntax::RSyntaxKind::IDENT; +use air_r_syntax::RSyntaxKind::R_BOGUS_FOR_VARIABLE; +use air_r_syntax::RSyntaxKind::R_DOT_DOT_I; +use air_r_syntax::RSyntaxKind::R_DOTS; +use air_r_syntax::RSyntaxKind::R_FOR_STATEMENT; +use air_r_syntax::RSyntaxKind::R_IDENTIFIER; +use air_r_syntax::RSyntaxKind::R_REPEAT_STATEMENT; +use air_r_syntax::RSyntaxKind::R_WHILE_STATEMENT; +use air_r_syntax::T; +use biome_parser::CompletedMarker; +use biome_parser::Parser; +use biome_parser::TokenSet; +use biome_parser::parse_recovery::ParseRecoveryTokenSet; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::Absent; +use biome_parser::parsed_syntax::ParsedSyntax::Present; +use biome_parser::token_set; + +/// Where a `for` variable may stop: at the `in` that follows it, or at an end +/// +/// Any closing delimiter, not just the one the caller opened, for the reason +/// [crate::syntax::call]'s `ARGUMENT_LIST_ENDS` gives. +const FOR_VARIABLE_ENDS: TokenSet = + token_set![T![EOF], T![in], T![')'], T![']'], T!["]]"], T!['}']]; + +pub(crate) fn parse_for_statement(p: &mut RParser) -> CompletedMarker { + let m = p.start(); + p.bump(T![for]); + + p.with_scope(Scope::Parenthesis, |p| { + p.expect(T!['(']); + parse_for_variable(p) + .or_recover_with_token_set( + p, + &ParseRecoveryTokenSet::new(R_BOGUS_FOR_VARIABLE, FOR_VARIABLE_ENDS), + diagnostics::expected_for_variable, + ) + .ok(); + p.expect(T![in]); + parse_expression(p).or_add_diagnostic(p, diagnostics::expected_for_sequence); + }); + p.expect(T![')']); + + parse_body_expression(p).or_add_diagnostic(p, diagnostics::expected_loop_body); + + m.complete(p, R_FOR_STATEMENT) +} + +/// identifier | `...` | `..i` +fn parse_for_variable(p: &mut RParser) -> ParsedSyntax { + match p.cur() { + IDENT => Present(parse_literal_expression(p, IDENT, R_IDENTIFIER)), + DOTS => Present(parse_literal_expression(p, DOTS, R_DOTS)), + DOTDOTI => Present(parse_literal_expression(p, DOTDOTI, R_DOT_DOT_I)), + _ => Absent, + } +} + +pub(crate) fn parse_while_statement(p: &mut RParser) -> CompletedMarker { + let m = p.start(); + p.bump(T![while]); + + p.with_scope(Scope::Parenthesis, |p| { + p.expect(T!['(']); + parse_expression(p).or_add_diagnostic(p, diagnostics::expected_condition); + }); + p.expect(T![')']); + + parse_body_expression(p).or_add_diagnostic(p, diagnostics::expected_loop_body); + + m.complete(p, R_WHILE_STATEMENT) +} + +pub(crate) fn parse_repeat_statement(p: &mut RParser) -> CompletedMarker { + let m = p.start(); + p.bump(T![repeat]); + + parse_body_expression(p).or_add_diagnostic(p, diagnostics::expected_loop_body); + + m.complete(p, R_REPEAT_STATEMENT) +} diff --git a/crates/air_r_parser/src/syntax/namespace.rs b/crates/air_r_parser/src/syntax/namespace.rs new file mode 100644 index 00000000..4e9d34f3 --- /dev/null +++ b/crates/air_r_parser/src/syntax/namespace.rs @@ -0,0 +1,43 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::syntax::selector::parse_selector; +use air_r_syntax::AnyRSelector; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::R_BOGUS_SELECTOR; +use air_r_syntax::RSyntaxKind::R_NAMESPACE_EXPRESSION; +use biome_parser::CompletedMarker; +use biome_parser::Parser; +use biome_rowan::AstNode; + +pub(crate) fn parse_namespace_expression( + p: &mut RParser, + mut lhs: CompletedMarker, + kind: RSyntaxKind, +) -> CompletedMarker { + let kind_range = p.cur_range(); + + // `::` and `:::` restrict their left operand as well, and it is already parsed + if !AnyRSelector::can_cast(lhs.kind(p)) { + p.error(diagnostics::expected_lhs_selector( + kind, + kind_range, + lhs.range(p), + )); + lhs = lhs.precede(p).complete(p, R_BOGUS_SELECTOR); + } + + let m = lhs.precede(p); + p.bump(kind); + + if p.scope().newlines_are_significant() && p.has_preceding_line_break() { + p.error(diagnostics::namespace_rhs_selector_after_line_break( + kind, kind_range, + )); + } else { + parse_selector(p).or_add_diagnostic(p, |_, range| { + diagnostics::expected_rhs_selector(kind, kind_range, range) + }); + } + + m.complete(p, R_NAMESPACE_EXPRESSION) +} diff --git a/crates/air_r_parser/src/syntax/parenthesized_expression.rs b/crates/air_r_parser/src/syntax/parenthesized_expression.rs new file mode 100644 index 00000000..2199ecf0 --- /dev/null +++ b/crates/air_r_parser/src/syntax/parenthesized_expression.rs @@ -0,0 +1,21 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::parser::Scope; +use crate::syntax::expr::parse_expression; +use air_r_syntax::RSyntaxKind::R_PARENTHESIZED_EXPRESSION; +use air_r_syntax::T; +use biome_parser::CompletedMarker; +use biome_parser::Parser; + +pub(crate) fn parse_parenthesized_expression(p: &mut RParser) -> CompletedMarker { + debug_assert!(p.at(T!['('])); + + let m = p.start(); + p.with_scope(Scope::Parenthesis, |p| { + p.bump(T!['(']); + parse_expression(p).or_add_diagnostic(p, diagnostics::expected_expression); + }); + p.expect(T![')']); + + m.complete(p, R_PARENTHESIZED_EXPRESSION) +} diff --git a/crates/air_r_parser/src/syntax/root.rs b/crates/air_r_parser/src/syntax/root.rs new file mode 100644 index 00000000..3aa9b302 --- /dev/null +++ b/crates/air_r_parser/src/syntax/root.rs @@ -0,0 +1,13 @@ +use crate::parser::RParser; +use crate::syntax::statement_list::parse_statement_list; +use air_r_syntax::RSyntaxKind::R_ROOT; +use air_r_syntax::T; +use biome_parser::Parser; + +pub(crate) fn parse_root(p: &mut RParser) { + let m = p.start(); + p.eat(T![UNICODE_BOM]); + parse_statement_list(p, T![EOF]); + p.expect(T![EOF]); + m.complete(p, R_ROOT); +} diff --git a/crates/air_r_parser/src/syntax/selector.rs b/crates/air_r_parser/src/syntax/selector.rs new file mode 100644 index 00000000..9c075fc7 --- /dev/null +++ b/crates/air_r_parser/src/syntax/selector.rs @@ -0,0 +1,32 @@ +use crate::parser::RParser; +use crate::syntax::literal::parse_literal_expression; +use air_r_syntax::RSyntaxKind::DOTDOTI; +use air_r_syntax::RSyntaxKind::DOTS; +use air_r_syntax::RSyntaxKind::IDENT; +use air_r_syntax::RSyntaxKind::R_DOT_DOT_I; +use air_r_syntax::RSyntaxKind::R_DOTS; +use air_r_syntax::RSyntaxKind::R_DOUBLE_QUOTED_STRING_LITERAL; +use air_r_syntax::RSyntaxKind::R_IDENTIFIER; +use air_r_syntax::RSyntaxKind::R_RAW_STRING_LITERAL; +use air_r_syntax::RSyntaxKind::R_SINGLE_QUOTED_STRING_LITERAL; +use air_r_syntax::RSyntaxKind::R_STRING_VALUE; +use biome_parser::Parser; +use biome_parser::parsed_syntax::ParsedSyntax; +use biome_parser::parsed_syntax::ParsedSyntax::Absent; +use biome_parser::parsed_syntax::ParsedSyntax::Present; + +/// identifier | string | `...` | `..i` +/// +/// Used by [crate::syntax::extract::parse_extract_expression()] and +/// [crate::syntax::namespace::parse_namespace_expression()] +pub(crate) fn parse_selector(p: &mut RParser) -> ParsedSyntax { + match p.cur() { + IDENT => Present(parse_literal_expression(p, IDENT, R_IDENTIFIER)), + DOTS => Present(parse_literal_expression(p, DOTS, R_DOTS)), + DOTDOTI => Present(parse_literal_expression(p, DOTDOTI, R_DOT_DOT_I)), + kind @ (R_SINGLE_QUOTED_STRING_LITERAL + | R_DOUBLE_QUOTED_STRING_LITERAL + | R_RAW_STRING_LITERAL) => Present(parse_literal_expression(p, kind, R_STRING_VALUE)), + _ => Absent, + } +} diff --git a/crates/air_r_parser/src/syntax/statement_list.rs b/crates/air_r_parser/src/syntax/statement_list.rs new file mode 100644 index 00000000..5e62aa6b --- /dev/null +++ b/crates/air_r_parser/src/syntax/statement_list.rs @@ -0,0 +1,83 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::parser::Scope; +use crate::syntax::expr::parse_expression; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::R_BOGUS_EXPRESSION; +use air_r_syntax::RSyntaxKind::R_EMPTY_STATEMENT; +use air_r_syntax::RSyntaxKind::R_EXPRESSION_STATEMENT; +use air_r_syntax::RSyntaxKind::R_STATEMENT_LIST; +use air_r_syntax::T; +use biome_parser::Parser; +use biome_parser::ParserProgress; +use biome_parser::parsed_syntax::ParsedSyntax::Absent; +use biome_parser::parsed_syntax::ParsedSyntax::Present; + +/// Runs to `close` and never stops short of it +/// +/// Used by [crate::syntax::root::parse_root()] and +/// [crate::syntax::braced_statements::parse_braced_statements()] +pub(crate) fn parse_statement_list(p: &mut RParser, close: RSyntaxKind) { + let statement_list = p.start(); + let mut progress = ParserProgress::default(); + + while !p.at(T![EOF]) && !p.at(close) { + progress.assert_progressing(p); + + let statement = p.start(); + + match p.cur() { + T![;] => { + if p.scope() == Scope::TopLevel { + // At top level, a semicolon must be preceded by an + // expression that ends on the same line, i.e. a lone `;` at + // top level isn't legal. Inside braces, semicolons may + // appear even without an expression, so `{ ; }`, `{ ;; }`, + // and `{ 1\n; }` are fine. + p.error(diagnostics::unexpected_semicolon(p.cur_range())); + } + p.bump(T![;]); + statement.complete(p, R_EMPTY_STATEMENT); + } + _ => { + match parse_expression(p) { + Present(expression) => { + if !p.at(T![EOF]) + && !p.at(close) + && !p.at(T![;]) + && !p.has_preceding_line_break() + { + p.error(diagnostics::expected_statement_separator( + expression.range(p), + p.cur_range(), + )); + } + } + Absent => { + // Consume everything until next statement separator as + // bogus, and always at least one token so that the + // statement list keeps progressing + let bogus = p.start(); + p.bump_any(); + while !p.at(T![EOF]) + && !p.at(close) + && !p.at(T![;]) + && !p.has_preceding_line_break() + { + p.bump_any(); + } + let bogus = bogus.complete(p, R_BOGUS_EXPRESSION); + p.error(diagnostics::expected_expression(p, bogus.range(p))); + } + } + + if !p.has_preceding_line_break() { + p.eat(T![;]); + } + statement.complete(p, R_EXPRESSION_STATEMENT); + } + } + } + + statement_list.complete(p, R_STATEMENT_LIST); +} diff --git a/crates/air_r_parser/src/syntax/unary.rs b/crates/air_r_parser/src/syntax/unary.rs new file mode 100644 index 00000000..51ae3f18 --- /dev/null +++ b/crates/air_r_parser/src/syntax/unary.rs @@ -0,0 +1,20 @@ +use crate::diagnostics; +use crate::parser::RParser; +use crate::syntax::binding_power::BindingPower; +use crate::syntax::expr::parse_expression_with_binding_power; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::R_UNARY_EXPRESSION; +use biome_parser::CompletedMarker; +use biome_parser::Parser; + +pub(crate) fn parse_unary_expression( + p: &mut RParser, + kind: RSyntaxKind, + binding_power: BindingPower, +) -> CompletedMarker { + let m = p.start(); + p.bump(kind); + parse_expression_with_binding_power(p, binding_power) + .or_add_diagnostic(p, diagnostics::expected_expression); + m.complete(p, R_UNARY_EXPRESSION) +} diff --git a/crates/air_r_parser/src/token_source.rs b/crates/air_r_parser/src/token_source.rs new file mode 100644 index 00000000..c8882bf9 --- /dev/null +++ b/crates/air_r_parser/src/token_source.rs @@ -0,0 +1,118 @@ +#[cfg(test)] +mod tests; + +use crate::lexer::RLexContext; +use crate::lexer::RLexer; +use crate::lexer::RReLexContext; +use air_r_syntax::RSyntaxKind; +use air_r_syntax::RSyntaxKind::EOF; +use biome_parser::diagnostic::ParseDiagnostic; +use biome_parser::lexer::BufferedLexer; +use biome_parser::prelude::TokenSource; +use biome_parser::prelude::Trivia; +use biome_parser::token_source::TokenSourceWithBufferedLexer; +use biome_rowan::TextRange; +use biome_rowan::TriviaPieceKind; + +pub(crate) struct RTokenSource<'src> { + lexer: BufferedLexer>, + + /// The trivia the CST is rebuilt from, in source order + trivia: Vec, +} + +impl<'src> RTokenSource<'src> { + pub(crate) fn from_str(source: &'src str) -> Self { + let lexer = BufferedLexer::new(RLexer::from_str(source)); + + let mut source = Self { + lexer, + trivia: Vec::new(), + }; + + source.next_non_trivia_token(true); + source + } + + /// Advances to the next non-trivia token, collecting the trivia it skips + /// + /// Trivia trails the token before it until a newline is seen, and leads the + /// token after it from the newline onward. Before the first token of the + /// file, everything leads. That aligns with Biome's parsers and is both + /// very logical and very practical because it doesn't require any + /// lookahead. + fn next_non_trivia_token(&mut self, first: bool) { + let mut trailing = !first; + + loop { + let kind = self.lexer.next_token(RLexContext::Regular); + + let Ok(trivia_kind) = TriviaPieceKind::try_from(kind) else { + break; + }; + + // We've now seen a newline, so this newline and everything after it + // is now considered leading trivia of the next token + if trivia_kind.is_newline() { + trailing = false; + } + + self.trivia + .push(Trivia::new(trivia_kind, self.current_range(), trailing)); + } + } + + /// Re-lexes the current token. Buffered lookahead is cleared if the kind + /// changes. + pub(crate) fn re_lex(&mut self, context: RReLexContext) -> RSyntaxKind { + self.lexer.re_lex(context) + } +} + +impl TokenSource for RTokenSource<'_> { + type Kind = RSyntaxKind; + + fn current(&self) -> Self::Kind { + self.lexer.current() + } + + fn current_range(&self) -> TextRange { + self.lexer.current_range() + } + + fn text(&self) -> &str { + self.lexer.source() + } + + fn has_preceding_line_break(&self) -> bool { + self.lexer.has_preceding_line_break() + } + + fn bump(&mut self) { + if self.current() != EOF { + self.next_non_trivia_token(false); + } + } + + fn skip_as_trivia(&mut self) { + if self.current() != EOF { + self.trivia.push(Trivia::new( + TriviaPieceKind::Skipped, + self.current_range(), + false, + )); + + self.next_non_trivia_token(true); + } + } + + fn finish(self) -> (Vec, Vec) { + (self.trivia, self.lexer.finish()) + } +} + +impl<'src> TokenSourceWithBufferedLexer> for RTokenSource<'src> { + fn lexer(&mut self) -> &mut BufferedLexer> { + &mut self.lexer + } +} diff --git a/crates/air_r_parser/src/token_source/tests.rs b/crates/air_r_parser/src/token_source/tests.rs new file mode 100644 index 00000000..897cf391 --- /dev/null +++ b/crates/air_r_parser/src/token_source/tests.rs @@ -0,0 +1,211 @@ +use super::*; +use air_r_syntax::RSyntaxKind::R_BRACK; +use air_r_syntax::RSyntaxKind::R_BRACK2; +use air_r_syntax::RSyntaxKind::R_DOUBLE_LITERAL; +use biome_parser::prelude::NthToken; +use biome_rowan::TextSize; + +enum Pos { + Leading, + Trailing, +} + +/// Drive the token source to `EOF` and hand back the trivia it collected +fn trivia(source: &str) -> Vec { + drain(RTokenSource::from_str(source)) +} + +fn drain(mut source: RTokenSource) -> Vec { + while source.current() != EOF { + source.bump(); + } + + source.finish().0 +} + +fn ws(start: u32, end: u32, position: Pos) -> Trivia { + piece(TriviaPieceKind::Whitespace, start, end, position) +} + +fn nl(start: u32, end: u32) -> Trivia { + piece(TriviaPieceKind::Newline, start, end, Pos::Leading) +} + +fn cmt(start: u32, end: u32, position: Pos) -> Trivia { + piece(TriviaPieceKind::SingleLineComment, start, end, position) +} + +fn skipped(start: u32, end: u32) -> Trivia { + piece(TriviaPieceKind::Skipped, start, end, Pos::Leading) +} + +fn piece(kind: TriviaPieceKind, start: u32, end: u32, position: Pos) -> Trivia { + let range = TextRange::new(TextSize::from(start), TextSize::from(end)); + Trivia::new(kind, range, matches!(position, Pos::Trailing)) +} + +#[track_caller] +fn assert_eq_trivia(actual: Vec, expected: Vec) { + // `Trivia` implements neither `PartialEq` nor a useful `Debug` + let key = |trivia: &Trivia| (trivia.kind(), trivia.text_range(), trivia.trailing()); + let actual: Vec<_> = actual.iter().map(key).collect(); + let expected: Vec<_> = expected.iter().map(key).collect(); + + assert_eq!(actual, expected); +} + +#[test] +fn test_parse_trivia_smoke_test() { + assert_eq_trivia( + trivia("1 + 1"), + vec![ws(1, 2, Pos::Trailing), ws(3, 4, Pos::Trailing)], + ); +} + +#[test] +fn test_parse_trivia_tab_test() { + assert_eq_trivia( + trivia("1\t+\t\n\t1"), + vec![ + ws(1, 2, Pos::Trailing), + ws(3, 4, Pos::Trailing), + nl(4, 5), + ws(5, 6, Pos::Leading), + ], + ); +} + +#[test] +fn test_parse_trivia_trailing_test() { + assert_eq_trivia( + trivia("1 + \n1"), + vec![ws(1, 2, Pos::Trailing), ws(3, 4, Pos::Trailing), nl(4, 5)], + ); + + assert_eq_trivia( + trivia("1 \n "), + vec![ws(1, 3, Pos::Trailing), nl(3, 4), ws(4, 5, Pos::Leading)], + ); + + assert_eq_trivia( + trivia("1 + \r\n1"), + vec![ws(1, 2, Pos::Trailing), ws(3, 4, Pos::Trailing), nl(4, 6)], + ); +} + +#[test] +fn test_parse_trivia_before_first_token() { + assert_eq_trivia(trivia(" \n1"), vec![ws(0, 2, Pos::Leading), nl(2, 3)]); +} + +#[test] +fn test_parse_trivia_comment_test() { + assert_eq_trivia( + trivia("1 #"), + vec![ws(1, 2, Pos::Trailing), cmt(2, 3, Pos::Trailing)], + ); +} + +#[test] +fn test_parse_trivia_comment_crlf_test() { + // Trailing comment: `1 # hi\r\n` (r-lib/tree-sitter-r#184) + assert_eq_trivia( + trivia("1 # hi\r\n"), + vec![ws(1, 2, Pos::Trailing), cmt(2, 6, Pos::Trailing), nl(6, 8)], + ); + + // Own-line comment: `# hi\r\n1` + assert_eq_trivia(trivia("# hi\r\n1"), vec![cmt(0, 4, Pos::Leading), nl(4, 6)]); +} + +#[test] +fn test_parse_trivia_crlf_leading_and_trailing_comments() { + assert_eq_trivia( + trivia("# Leading\r\n1 + 1 # Trailing\r\n# Leading\r\n"), + vec![ + cmt(0, 9, Pos::Leading), + nl(9, 11), + ws(12, 13, Pos::Trailing), + ws(14, 15, Pos::Trailing), + ws(16, 17, Pos::Trailing), + cmt(17, 27, Pos::Trailing), + nl(27, 29), + cmt(29, 38, Pos::Leading), + nl(38, 40), + ], + ); +} + +#[test] +fn test_parse_trivia_comment_nothing_else_test() { + assert_eq_trivia(trivia("#"), vec![cmt(0, 1, Pos::Leading)]); +} + +#[test] +fn test_parse_trivia_comment_end_of_document_test() { + assert_eq_trivia(trivia("1\n#"), vec![nl(1, 2), cmt(2, 3, Pos::Leading)]); +} + +#[test] +fn test_parse_trivia_whitespace_between_comments_test() { + assert_eq_trivia( + trivia("1 #\n#\n2"), + vec![ + ws(1, 2, Pos::Trailing), + cmt(2, 3, Pos::Trailing), + nl(3, 4), + cmt(4, 5, Pos::Leading), + nl(5, 6), + ], + ); +} + +#[test] +fn test_parse_trivia_comment_beginning_of_document_test() { + assert_eq_trivia(trivia("#\n1"), vec![cmt(0, 1, Pos::Leading), nl(1, 2)]); +} + +/// A skipped token becomes a `Skipped` piece, and everything up to the next +/// token leads that token rather than trailing what was skipped +#[test] +fn test_skip_as_trivia() { + let mut source = RTokenSource::from_str("1 2"); + source.skip_as_trivia(); + + assert_eq!(source.current(), R_DOUBLE_LITERAL); + assert_eq!(source.current_range(), TextRange::new(2.into(), 3.into())); + + assert_eq_trivia(drain(source), vec![skipped(0, 1), ws(1, 2, Pos::Leading)]); +} + +/// Lookahead drives the inner lexer past the current token, so using +/// [`BufferedLexer`] is useful to allow us to still answer for the token the +/// parser is actually on. +#[test] +fn test_line_break_survives_lookahead() { + let mut source = RTokenSource::from_str("1\n2"); + assert!(!source.has_preceding_line_break()); + + assert_eq!(source.nth(1), R_DOUBLE_LITERAL); + assert!(source.has_nth_preceding_line_break(1)); + + assert_eq!(source.current_range(), TextRange::new(0.into(), 1.into())); + assert!(!source.has_preceding_line_break()); + + source.bump(); + assert!(source.has_preceding_line_break()); +} + +/// `parse_argument()`'s lookahead buffers the token after an argument name, +/// which inside `[[` is the `]` that the next bump asks to join +#[test] +fn test_re_lex_after_lookahead() { + let mut source = RTokenSource::from_str("a]]"); + assert_eq!(source.nth(1), R_BRACK); + + source.bump(); + assert_eq!(source.current(), R_BRACK); + + assert_eq!(source.re_lex(RReLexContext::JoinBrackets), R_BRACK2); + assert_eq!(source.current_range(), TextRange::new(1.into(), 3.into())); +} diff --git a/crates/air_r_parser/src/treesitter.rs b/crates/air_r_parser/src/treesitter.rs deleted file mode 100644 index fccc82d2..00000000 --- a/crates/air_r_parser/src/treesitter.rs +++ /dev/null @@ -1,196 +0,0 @@ -use air_r_syntax::RSyntaxKind; -use tree_sitter::{Node, TreeCursor}; - -/// `WalkEvent` describes tree walking process. -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum WalkEvent { - /// Fired before traversing the node. - Enter(T), - /// Fired after the node is traversed. - Leave(T), -} - -impl WalkEvent { - pub fn map U, U>(self, f: F) -> WalkEvent { - match self { - WalkEvent::Enter(it) => WalkEvent::Enter(f(it)), - WalkEvent::Leave(it) => WalkEvent::Leave(f(it)), - } - } -} - -// TODO: Assign iterator to rowan -pub struct Preorder<'tree> { - cursor: TreeCursor<'tree>, - next: Option>>, -} - -impl<'tree> Preorder<'tree> { - fn new(node: Node) -> Preorder { - let cursor = node.walk(); - let next = Some(WalkEvent::Enter(node)); - Preorder { cursor, next } - } - - /// Peek at the upcoming node without advancing to it - /// - /// NOTE: Effectively free, since we track this anyways - pub fn peek(&self) -> &Option>> { - &self.next - } - - /// Peek at the upcoming node's field name - /// - /// NOTE: The `self.cursor` is kept in sync with `self.next` and is always - /// pointing to the upcoming node, so using the cursor to extract the field - /// name is always valid. - pub fn peek_field_name(&self) -> Option<&'static str> { - self.cursor.field_name() - } - - pub fn skip_subtree(&mut self) { - let next = self.next.take(); - self.next = next.as_ref().and_then(|next| { - Some(match next { - WalkEvent::Enter(_first_child) => match self.cursor.goto_parent() { - true => WalkEvent::Leave(self.cursor.node()), - false => return None, - }, - WalkEvent::Leave(parent) => WalkEvent::Leave(*parent), - }) - }); - } -} - -impl<'tree> Iterator for Preorder<'tree> { - type Item = WalkEvent>; - - fn next(&mut self) -> Option { - let next = self.next.take(); - self.next = next.as_ref().and_then(|next| { - Some(match next { - WalkEvent::Enter(node) => match self.cursor.goto_first_child() { - true => WalkEvent::Enter(self.cursor.node()), - false => WalkEvent::Leave(*node), - }, - WalkEvent::Leave(node) => match self.cursor.goto_next_sibling() { - true => WalkEvent::Enter(self.cursor.node()), - false => match self.cursor.goto_parent() { - true => WalkEvent::Leave(self.cursor.node()), - false => return None, - }, - }, - }) - }); - next - } -} - -fn node_syntax_kind(x: &Node) -> RSyntaxKind { - match x.kind() { - "program" => RSyntaxKind::R_ROOT, - "unary_operator" => RSyntaxKind::R_UNARY_EXPRESSION, - "binary_operator" => RSyntaxKind::R_BINARY_EXPRESSION, - "extract_operator" => RSyntaxKind::R_EXTRACT_EXPRESSION, - "namespace_operator" => RSyntaxKind::R_NAMESPACE_EXPRESSION, - "function_definition" => RSyntaxKind::R_FUNCTION_DEFINITION, - "parameters" => RSyntaxKind::R_PARAMETERS, - "parameter" => RSyntaxKind::R_PARAMETER, - "if_statement" => RSyntaxKind::R_IF_STATEMENT, - "for_statement" => RSyntaxKind::R_FOR_STATEMENT, - "while_statement" => RSyntaxKind::R_WHILE_STATEMENT, - "repeat_statement" => RSyntaxKind::R_REPEAT_STATEMENT, - "braced_expression" => RSyntaxKind::R_BRACED_STATEMENTS, - "parenthesized_expression" => RSyntaxKind::R_PARENTHESIZED_EXPRESSION, - "call" => RSyntaxKind::R_CALL, - "subset" => RSyntaxKind::R_SUBSET, - "subset2" => RSyntaxKind::R_SUBSET2, - "argument" => RSyntaxKind::R_ARGUMENT, - "identifier" => RSyntaxKind::R_IDENTIFIER, - "integer" => RSyntaxKind::R_INTEGER_VALUE, - "float" => RSyntaxKind::R_DOUBLE_VALUE, - "complex" => RSyntaxKind::R_COMPLEX_VALUE, - "string" => RSyntaxKind::R_STRING_VALUE, - "next" => RSyntaxKind::R_NEXT_EXPRESSION, - "break" => RSyntaxKind::R_BREAK_EXPRESSION, - "true" => RSyntaxKind::R_TRUE_EXPRESSION, - "false" => RSyntaxKind::R_FALSE_EXPRESSION, - "null" => RSyntaxKind::R_NULL_EXPRESSION, - "inf" => RSyntaxKind::R_INF_EXPRESSION, - "nan" => RSyntaxKind::R_NAN_EXPRESSION, - "na" => RSyntaxKind::R_NA_EXPRESSION, - "NA" => RSyntaxKind::NA_LOGICAL_KW, - "NA_integer_" => RSyntaxKind::NA_INTEGER_KW, - "NA_real_" => RSyntaxKind::NA_DOUBLE_KW, - "NA_complex_" => RSyntaxKind::NA_COMPLEX_KW, - "NA_character_" => RSyntaxKind::NA_CHARACTER_KW, - "{" => RSyntaxKind::L_CURLY, - "}" => RSyntaxKind::R_CURLY, - "[" => RSyntaxKind::L_BRACK, - "]" => RSyntaxKind::R_BRACK, - "[[" => RSyntaxKind::L_BRACK2, - "]]" => RSyntaxKind::R_BRACK2, - "(" => RSyntaxKind::L_PAREN, - ")" => RSyntaxKind::R_PAREN, - "?" => RSyntaxKind::WAT, - "~" => RSyntaxKind::TILDE, - "<-" => RSyntaxKind::ASSIGN, - "<<-" => RSyntaxKind::SUPER_ASSIGN, - ":=" => RSyntaxKind::WALRUS, - "->" => RSyntaxKind::ASSIGN_RIGHT, - "->>" => RSyntaxKind::SUPER_ASSIGN_RIGHT, - "=" => RSyntaxKind::EQUAL, - "|" => RSyntaxKind::OR, - "&" => RSyntaxKind::AND, - "||" => RSyntaxKind::OR2, - "&&" => RSyntaxKind::AND2, - "<" => RSyntaxKind::LESS_THAN, - "<=" => RSyntaxKind::LESS_THAN_OR_EQUAL_TO, - ">" => RSyntaxKind::GREATER_THAN, - ">=" => RSyntaxKind::GREATER_THAN_OR_EQUAL_TO, - "==" => RSyntaxKind::EQUAL2, - "!=" => RSyntaxKind::NOT_EQUAL, - "+" => RSyntaxKind::PLUS, - "-" => RSyntaxKind::MINUS, - "*" => RSyntaxKind::MULTIPLY, - "/" => RSyntaxKind::DIVIDE, - "^" => RSyntaxKind::EXPONENTIATE, - "**" => RSyntaxKind::EXPONENTIATE2, - "|>" => RSyntaxKind::PIPE, - "special" => RSyntaxKind::SPECIAL, - ":" => RSyntaxKind::COLON, - "::" => RSyntaxKind::COLON2, - ":::" => RSyntaxKind::COLON3, - "$" => RSyntaxKind::DOLLAR, - "@" => RSyntaxKind::AT, - "!" => RSyntaxKind::BANG, - "function" => RSyntaxKind::FUNCTION_KW, - "\\" => RSyntaxKind::BACKSLASH, - "if" => RSyntaxKind::IF_KW, - "else" => RSyntaxKind::ELSE_KW, - "for" => RSyntaxKind::FOR_KW, - "in" => RSyntaxKind::IN_KW, - "while" => RSyntaxKind::WHILE_KW, - "repeat" => RSyntaxKind::REPEAT_KW, - "comma" => RSyntaxKind::COMMA, - "dots" => RSyntaxKind::R_DOTS, - "dot_dot_i" => RSyntaxKind::R_DOT_DOT_I, - "comment" => RSyntaxKind::COMMENT, - kind => unreachable!("Not implemented: '{kind}'."), - } -} - -pub trait NodeTypeExt: Sized { - fn syntax_kind(&self) -> RSyntaxKind; - fn preorder(&self) -> Preorder<'_>; -} - -impl NodeTypeExt for Node<'_> { - fn syntax_kind(&self) -> RSyntaxKind { - node_syntax_kind(self) - } - - fn preorder(&self) -> Preorder<'_> { - Preorder::new(*self) - } -} diff --git a/crates/air_r_parser/tests/snapshots/error/call/side_by_side_arguments.R b/crates/air_r_parser/tests/snapshots/error/call/side_by_side_arguments.R deleted file mode 100644 index 20434796..00000000 --- a/crates/air_r_parser/tests/snapshots/error/call/side_by_side_arguments.R +++ /dev/null @@ -1,2 +0,0 @@ -# Can't have two arguments side by side without a delimiter -fn(a b) diff --git a/crates/air_r_parser/tests/snapshots/error/call/side_by_side_arguments.R.snap b/crates/air_r_parser/tests/snapshots/error/call/side_by_side_arguments.R.snap deleted file mode 100644 index fa360239..00000000 --- a/crates/air_r_parser/tests/snapshots/error/call/side_by_side_arguments.R.snap +++ /dev/null @@ -1,51 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -# Can't have two arguments side by side without a delimiter -fn(a b) - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBogusExpression { - items: [ - R_BOGUS@0..68 "# Can't have two arguments side by side without a delimiter\nfn(a b)\n" [] [], - ], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@68..68 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..68 - 0: (empty) - 1: R_STATEMENT_LIST@0..68 - 0: R_EXPRESSION_STATEMENT@0..68 - 0: R_BOGUS_EXPRESSION@0..68 - 0: R_BOGUS@0..68 "# Can't have two arguments side by side without a delimiter\nfn(a b)\n" [] [] - 1: (empty) - 2: EOF@68..68 "" [] [] - -``` - -## Errors - -``` -Failed to parse due to syntax errors. -``` diff --git a/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_double_colon.R b/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_double_colon.R deleted file mode 100644 index 66b9b916..00000000 --- a/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_double_colon.R +++ /dev/null @@ -1 +0,0 @@ -a()::b \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_double_colon.R.snap b/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_double_colon.R.snap deleted file mode 100644 index c44c3722..00000000 --- a/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_double_colon.R.snap +++ /dev/null @@ -1,49 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -a()::b -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBogusExpression { - items: [ - R_BOGUS@0..6 "a()::b" [] [], - ], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@6..6 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..6 - 0: (empty) - 1: R_STATEMENT_LIST@0..6 - 0: R_EXPRESSION_STATEMENT@0..6 - 0: R_BOGUS_EXPRESSION@0..6 - 0: R_BOGUS@0..6 "a()::b" [] [] - 1: (empty) - 2: EOF@6..6 "" [] [] - -``` - -## Errors - -``` -Failed to parse due to syntax errors. -``` diff --git a/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_triple_colon.R b/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_triple_colon.R deleted file mode 100644 index 3d1a4ea8..00000000 --- a/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_triple_colon.R +++ /dev/null @@ -1 +0,0 @@ -a():::b \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_triple_colon.R.snap b/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_triple_colon.R.snap deleted file mode 100644 index e4776c06..00000000 --- a/crates/air_r_parser/tests/snapshots/error/namespace_expression/call_lhs_triple_colon.R.snap +++ /dev/null @@ -1,49 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -a():::b -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBogusExpression { - items: [ - R_BOGUS@0..7 "a():::b" [] [], - ], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@7..7 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..7 - 0: (empty) - 1: R_STATEMENT_LIST@0..7 - 0: R_EXPRESSION_STATEMENT@0..7 - 0: R_BOGUS_EXPRESSION@0..7 - 0: R_BOGUS@0..7 "a():::b" [] [] - 1: (empty) - 2: EOF@7..7 "" [] [] - -``` - -## Errors - -``` -Failed to parse due to syntax errors. -``` diff --git a/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_double_colon.R b/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_double_colon.R deleted file mode 100644 index e657d86d..00000000 --- a/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_double_colon.R +++ /dev/null @@ -1 +0,0 @@ -a::b::c \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_double_colon.R.snap b/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_double_colon.R.snap deleted file mode 100644 index 2806b5ca..00000000 --- a/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_double_colon.R.snap +++ /dev/null @@ -1,49 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -a::b::c -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBogusExpression { - items: [ - R_BOGUS@0..7 "a::b::c" [] [], - ], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@7..7 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..7 - 0: (empty) - 1: R_STATEMENT_LIST@0..7 - 0: R_EXPRESSION_STATEMENT@0..7 - 0: R_BOGUS_EXPRESSION@0..7 - 0: R_BOGUS@0..7 "a::b::c" [] [] - 1: (empty) - 2: EOF@7..7 "" [] [] - -``` - -## Errors - -``` -Failed to parse due to syntax errors. -``` diff --git a/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_triple_colon.R b/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_triple_colon.R deleted file mode 100644 index 280e1e53..00000000 --- a/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_triple_colon.R +++ /dev/null @@ -1 +0,0 @@ -a:::b:::c \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_triple_colon.R.snap b/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_triple_colon.R.snap deleted file mode 100644 index 6c076682..00000000 --- a/crates/air_r_parser/tests/snapshots/error/namespace_expression/chained_triple_colon.R.snap +++ /dev/null @@ -1,49 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -a:::b:::c -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBogusExpression { - items: [ - R_BOGUS@0..9 "a:::b:::c" [] [], - ], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@9..9 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..9 - 0: (empty) - 1: R_STATEMENT_LIST@0..9 - 0: R_EXPRESSION_STATEMENT@0..9 - 0: R_BOGUS_EXPRESSION@0..9 - 0: R_BOGUS@0..9 "a:::b:::c" [] [] - 1: (empty) - 2: EOF@9..9 "" [] [] - -``` - -## Errors - -``` -Failed to parse due to syntax errors. -``` diff --git a/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/empty.R b/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/empty.R deleted file mode 100644 index dd626a0f..00000000 --- a/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/empty.R +++ /dev/null @@ -1 +0,0 @@ -() \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/empty.R.snap b/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/empty.R.snap deleted file mode 100644 index f06d3f6b..00000000 --- a/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/empty.R.snap +++ /dev/null @@ -1,49 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -() -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBogusExpression { - items: [ - R_BOGUS@0..2 "()" [] [], - ], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@2..2 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..2 - 0: (empty) - 1: R_STATEMENT_LIST@0..2 - 0: R_EXPRESSION_STATEMENT@0..2 - 0: R_BOGUS_EXPRESSION@0..2 - 0: R_BOGUS@0..2 "()" [] [] - 1: (empty) - 2: EOF@2..2 "" [] [] - -``` - -## Errors - -``` -Failed to parse due to syntax errors. -``` diff --git a/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/multiple.R b/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/multiple.R deleted file mode 100644 index 8d2dedd1..00000000 --- a/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/multiple.R +++ /dev/null @@ -1,4 +0,0 @@ -( - a - b -) \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/multiple.R.snap b/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/multiple.R.snap deleted file mode 100644 index 59a67800..00000000 --- a/crates/air_r_parser/tests/snapshots/error/parenthesized_expression/multiple.R.snap +++ /dev/null @@ -1,52 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -( - a - b -) -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBogusExpression { - items: [ - R_BOGUS@0..11 "(\n a\n b\n)" [] [], - ], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@11..11 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..11 - 0: (empty) - 1: R_STATEMENT_LIST@0..11 - 0: R_EXPRESSION_STATEMENT@0..11 - 0: R_BOGUS_EXPRESSION@0..11 - 0: R_BOGUS@0..11 "(\n a\n b\n)" [] [] - 1: (empty) - 2: EOF@11..11 "" [] [] - -``` - -## Errors - -``` -Failed to parse due to syntax errors. -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/braced_expressions.R.snap b/crates/air_r_parser/tests/snapshots/ok/braced_expressions.R.snap deleted file mode 100644 index a7a874c9..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/braced_expressions.R.snap +++ /dev/null @@ -1,193 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -{ } - -{ - -} - -{ 1 } - -{ - 1 - 2 + 2 - 3 + - 4 -} - -# comment1 -{ # comment2 - 1 # comment3 -} # comment4 - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@0..2 "{" [] [Whitespace(" ")], - statements: RStatementList [], - r_curly_token: R_CURLY@2..3 "}" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@3..6 "{" [Newline("\n"), Newline("\n")] [], - statements: RStatementList [], - r_curly_token: R_CURLY@6..9 "}" [Newline("\n"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@9..13 "{" [Newline("\n"), Newline("\n")] [Whitespace(" ")], - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@13..15 "1" [] [Whitespace(" ")], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@15..16 "}" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@16..19 "{" [Newline("\n"), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@19..23 "1" [Newline("\n"), Whitespace(" ")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RDoubleValue { - value_token: R_DOUBLE_LITERAL@23..28 "2" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: PLUS@28..30 "+" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@30..31 "2" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RDoubleValue { - value_token: R_DOUBLE_LITERAL@31..36 "3" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: PLUS@36..37 "+" [] [], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@37..43 "4" [Newline("\n"), Whitespace(" ")] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@43..45 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@45..70 "{" [Newline("\n"), Newline("\n"), Comments("# comment1"), Newline("\n")] [Whitespace(" "), Comments("# comment2")], - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@70..85 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@85..98 "}" [Newline("\n")] [Whitespace(" "), Comments("# comment4")], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@98..99 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..99 - 0: (empty) - 1: R_STATEMENT_LIST@0..98 - 0: R_EXPRESSION_STATEMENT@0..3 - 0: R_BRACED_STATEMENTS@0..3 - 0: L_CURLY@0..2 "{" [] [Whitespace(" ")] - 1: R_STATEMENT_LIST@2..2 - 2: R_CURLY@2..3 "}" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@3..9 - 0: R_BRACED_STATEMENTS@3..9 - 0: L_CURLY@3..6 "{" [Newline("\n"), Newline("\n")] [] - 1: R_STATEMENT_LIST@6..6 - 2: R_CURLY@6..9 "}" [Newline("\n"), Newline("\n")] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@9..16 - 0: R_BRACED_STATEMENTS@9..16 - 0: L_CURLY@9..13 "{" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: R_STATEMENT_LIST@13..15 - 0: R_EXPRESSION_STATEMENT@13..15 - 0: R_DOUBLE_VALUE@13..15 - 0: R_DOUBLE_LITERAL@13..15 "1" [] [Whitespace(" ")] - 1: (empty) - 2: R_CURLY@15..16 "}" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@16..45 - 0: R_BRACED_STATEMENTS@16..45 - 0: L_CURLY@16..19 "{" [Newline("\n"), Newline("\n")] [] - 1: R_STATEMENT_LIST@19..43 - 0: R_EXPRESSION_STATEMENT@19..23 - 0: R_DOUBLE_VALUE@19..23 - 0: R_DOUBLE_LITERAL@19..23 "1" [Newline("\n"), Whitespace(" ")] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@23..31 - 0: R_BINARY_EXPRESSION@23..31 - 0: R_DOUBLE_VALUE@23..28 - 0: R_DOUBLE_LITERAL@23..28 "2" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: PLUS@28..30 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@30..31 - 0: R_DOUBLE_LITERAL@30..31 "2" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@31..43 - 0: R_BINARY_EXPRESSION@31..43 - 0: R_DOUBLE_VALUE@31..36 - 0: R_DOUBLE_LITERAL@31..36 "3" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: PLUS@36..37 "+" [] [] - 2: R_DOUBLE_VALUE@37..43 - 0: R_DOUBLE_LITERAL@37..43 "4" [Newline("\n"), Whitespace(" ")] [] - 1: (empty) - 2: R_CURLY@43..45 "}" [Newline("\n")] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@45..98 - 0: R_BRACED_STATEMENTS@45..98 - 0: L_CURLY@45..70 "{" [Newline("\n"), Newline("\n"), Comments("# comment1"), Newline("\n")] [Whitespace(" "), Comments("# comment2")] - 1: R_STATEMENT_LIST@70..85 - 0: R_EXPRESSION_STATEMENT@70..85 - 0: R_DOUBLE_VALUE@70..85 - 0: R_DOUBLE_LITERAL@70..85 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")] - 1: (empty) - 2: R_CURLY@85..98 "}" [Newline("\n")] [Whitespace(" "), Comments("# comment4")] - 1: (empty) - 2: EOF@98..99 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/calls.R b/crates/air_r_parser/tests/snapshots/ok/calls.R deleted file mode 100644 index 2e5a47b2..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/calls.R +++ /dev/null @@ -1,58 +0,0 @@ -fn() -fn(a) -fn(a, b) -fn(...) -fn(a, ..., b) - -fn(a = 1) -fn(a = ) - -fn(a = 1, b = 2) -fn(a = , b = 2) - -fn(... = 1) -fn(... =) - -fn(..1 = 2) -fn(..1 =) - -fn("arg" = 1) -fn("arg" =) - -# Comma tests -fn(,) -fn(,,,) -fn(,,a,,b,,) - -# Comment tests -fn( - # comment -) -fn(, - # comment -,) -fn( - a, # comment1 - b # comment2 -) -fn( - a # comment1 - = # comment2 - 1 # comment3 -) - -{expr}(a = 1) - -# `NULL` argument name (r-lib/tree-sitter-r#164) -fn( - NULL = 1 -) -fn( - NULL = -) -fn( - NULL = , -) - -# Standard call as of https://github.com/r-lib/tree-sitter-r/pull/197 -return() \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/ok/calls.R.snap b/crates/air_r_parser/tests/snapshots/ok/calls.R.snap deleted file mode 100644 index feeea444..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/calls.R.snap +++ /dev/null @@ -1,1259 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -fn() -fn(a) -fn(a, b) -fn(...) -fn(a, ..., b) - -fn(a = 1) -fn(a = ) - -fn(a = 1, b = 2) -fn(a = , b = 2) - -fn(... = 1) -fn(... =) - -fn(..1 = 2) -fn(..1 =) - -fn("arg" = 1) -fn("arg" =) - -# Comma tests -fn(,) -fn(,,,) -fn(,,a,,b,,) - -# Comment tests -fn( - # comment -) -fn(, - # comment -,) -fn( - a, # comment1 - b # comment2 -) -fn( - a # comment1 - = # comment2 - 1 # comment3 -) - -{expr}(a = 1) - -# `NULL` argument name (r-lib/tree-sitter-r#164) -fn( - NULL = 1 -) -fn( - NULL = -) -fn( - NULL = , -) - -# Standard call as of https://github.com/r-lib/tree-sitter-r/pull/197 -return() -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@0..2 "fn" [] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@2..3 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@3..4 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@4..7 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@7..8 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@8..9 "a" [] [], - }, - }, - ], - r_paren_token: R_PAREN@9..10 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@10..13 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@13..14 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@14..15 "a" [] [], - }, - }, - COMMA@15..17 "," [] [Whitespace(" ")], - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@17..18 "b" [] [], - }, - }, - ], - r_paren_token: R_PAREN@18..19 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@19..22 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@22..23 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RDots { - value_token: DOTS@23..26 "..." [] [], - }, - }, - ], - r_paren_token: R_PAREN@26..27 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@27..30 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@30..31 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@31..32 "a" [] [], - }, - }, - COMMA@32..34 "," [] [Whitespace(" ")], - RArgument { - name_clause: missing (optional), - value: RDots { - value_token: DOTS@34..37 "..." [] [], - }, - }, - COMMA@37..39 "," [] [Whitespace(" ")], - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@39..40 "b" [] [], - }, - }, - ], - r_paren_token: R_PAREN@40..41 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@41..45 "fn" [Newline("\n"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@45..46 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RIdentifier { - name_token: IDENT@46..48 "a" [] [Whitespace(" ")], - }, - eq_token: EQUAL@48..50 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@50..51 "1" [] [], - }, - }, - ], - r_paren_token: R_PAREN@51..52 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@52..55 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@55..56 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RIdentifier { - name_token: IDENT@56..58 "a" [] [Whitespace(" ")], - }, - eq_token: EQUAL@58..60 "=" [] [Whitespace(" ")], - }, - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@60..61 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@61..65 "fn" [Newline("\n"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@65..66 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RIdentifier { - name_token: IDENT@66..68 "a" [] [Whitespace(" ")], - }, - eq_token: EQUAL@68..70 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@70..71 "1" [] [], - }, - }, - COMMA@71..73 "," [] [Whitespace(" ")], - RArgument { - name_clause: RArgumentNameClause { - name: RIdentifier { - name_token: IDENT@73..75 "b" [] [Whitespace(" ")], - }, - eq_token: EQUAL@75..77 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@77..78 "2" [] [], - }, - }, - ], - r_paren_token: R_PAREN@78..79 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@79..82 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@82..83 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RIdentifier { - name_token: IDENT@83..85 "a" [] [Whitespace(" ")], - }, - eq_token: EQUAL@85..87 "=" [] [Whitespace(" ")], - }, - value: missing (optional), - }, - COMMA@87..89 "," [] [Whitespace(" ")], - RArgument { - name_clause: RArgumentNameClause { - name: RIdentifier { - name_token: IDENT@89..91 "b" [] [Whitespace(" ")], - }, - eq_token: EQUAL@91..93 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@93..94 "2" [] [], - }, - }, - ], - r_paren_token: R_PAREN@94..95 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@95..99 "fn" [Newline("\n"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@99..100 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RDots { - value_token: DOTS@100..104 "..." [] [Whitespace(" ")], - }, - eq_token: EQUAL@104..106 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@106..107 "1" [] [], - }, - }, - ], - r_paren_token: R_PAREN@107..108 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@108..111 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@111..112 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RDots { - value_token: DOTS@112..116 "..." [] [Whitespace(" ")], - }, - eq_token: EQUAL@116..117 "=" [] [], - }, - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@117..118 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@118..122 "fn" [Newline("\n"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@122..123 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RDotDotI { - value_token: DOTDOTI@123..127 "..1" [] [Whitespace(" ")], - }, - eq_token: EQUAL@127..129 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@129..130 "2" [] [], - }, - }, - ], - r_paren_token: R_PAREN@130..131 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@131..134 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@134..135 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RDotDotI { - value_token: DOTDOTI@135..139 "..1" [] [Whitespace(" ")], - }, - eq_token: EQUAL@139..140 "=" [] [], - }, - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@140..141 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@141..145 "fn" [Newline("\n"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@145..146 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@146..152 "\"arg\"" [] [Whitespace(" ")], - }, - eq_token: EQUAL@152..154 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@154..155 "1" [] [], - }, - }, - ], - r_paren_token: R_PAREN@155..156 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@156..159 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@159..160 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@160..166 "\"arg\"" [] [Whitespace(" ")], - }, - eq_token: EQUAL@166..167 "=" [] [], - }, - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@167..168 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@168..186 "fn" [Newline("\n"), Newline("\n"), Comments("# Comma tests"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@186..187 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@187..188 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@188..189 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@189..192 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@192..193 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@193..194 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@194..195 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@195..196 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@196..197 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@197..200 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@200..201 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@201..202 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@202..203 "," [] [], - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@203..204 "a" [] [], - }, - }, - COMMA@204..205 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@205..206 "," [] [], - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@206..207 "b" [] [], - }, - }, - COMMA@207..208 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@208..209 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@209..210 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@210..230 "fn" [Newline("\n"), Newline("\n"), Comments("# Comment tests"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@230..231 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@231..245 ")" [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@245..248 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@248..249 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@249..250 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - COMMA@250..264 "," [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@264..265 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@265..268 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@268..269 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@269..273 "a" [Newline("\n"), Whitespace(" ")] [], - }, - }, - COMMA@273..285 "," [] [Whitespace(" "), Comments("# comment1")], - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@285..300 "b" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")], - }, - }, - ], - r_paren_token: R_PAREN@300..302 ")" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@302..305 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@305..306 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RIdentifier { - name_token: IDENT@306..321 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment1")], - }, - eq_token: EQUAL@321..336 "=" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@336..351 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")], - }, - }, - ], - r_paren_token: R_PAREN@351..353 ")" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RBracedStatements { - l_curly_token: L_CURLY@353..356 "{" [Newline("\n"), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RIdentifier { - name_token: IDENT@356..360 "expr" [] [], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@360..361 "}" [] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@361..362 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RIdentifier { - name_token: IDENT@362..364 "a" [] [Whitespace(" ")], - }, - eq_token: EQUAL@364..366 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@366..367 "1" [] [], - }, - }, - ], - r_paren_token: R_PAREN@367..368 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@368..421 "fn" [Newline("\n"), Newline("\n"), Comments("# `NULL` argument nam ..."), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@421..422 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RNullExpression { - null_token: NULL_KW@422..430 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - eq_token: EQUAL@430..432 "=" [] [Whitespace(" ")], - }, - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@432..433 "1" [] [], - }, - }, - ], - r_paren_token: R_PAREN@433..435 ")" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@435..438 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@438..439 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RNullExpression { - null_token: NULL_KW@439..447 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - eq_token: EQUAL@447..449 "=" [] [Whitespace(" ")], - }, - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@449..451 ")" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@451..454 "fn" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@454..455 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: RArgumentNameClause { - name: RNullExpression { - null_token: NULL_KW@455..463 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - eq_token: EQUAL@463..465 "=" [] [Whitespace(" ")], - }, - value: missing (optional), - }, - COMMA@465..466 "," [] [], - RArgument { - name_clause: missing (optional), - value: missing (optional), - }, - ], - r_paren_token: R_PAREN@466..468 ")" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RIdentifier { - name_token: IDENT@468..546 "return" [Newline("\n"), Newline("\n"), Comments("# Standard call as of ..."), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@546..547 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@547..548 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@548..548 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..548 - 0: (empty) - 1: R_STATEMENT_LIST@0..548 - 0: R_EXPRESSION_STATEMENT@0..4 - 0: R_CALL@0..4 - 0: R_IDENTIFIER@0..2 - 0: IDENT@0..2 "fn" [] [] - 1: R_CALL_ARGUMENTS@2..4 - 0: L_PAREN@2..3 "(" [] [] - 1: R_ARGUMENT_LIST@3..3 - 2: R_PAREN@3..4 ")" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@4..10 - 0: R_CALL@4..10 - 0: R_IDENTIFIER@4..7 - 0: IDENT@4..7 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@7..10 - 0: L_PAREN@7..8 "(" [] [] - 1: R_ARGUMENT_LIST@8..9 - 0: R_ARGUMENT@8..9 - 0: (empty) - 1: R_IDENTIFIER@8..9 - 0: IDENT@8..9 "a" [] [] - 2: R_PAREN@9..10 ")" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@10..19 - 0: R_CALL@10..19 - 0: R_IDENTIFIER@10..13 - 0: IDENT@10..13 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@13..19 - 0: L_PAREN@13..14 "(" [] [] - 1: R_ARGUMENT_LIST@14..18 - 0: R_ARGUMENT@14..15 - 0: (empty) - 1: R_IDENTIFIER@14..15 - 0: IDENT@14..15 "a" [] [] - 1: COMMA@15..17 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@17..18 - 0: (empty) - 1: R_IDENTIFIER@17..18 - 0: IDENT@17..18 "b" [] [] - 2: R_PAREN@18..19 ")" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@19..27 - 0: R_CALL@19..27 - 0: R_IDENTIFIER@19..22 - 0: IDENT@19..22 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@22..27 - 0: L_PAREN@22..23 "(" [] [] - 1: R_ARGUMENT_LIST@23..26 - 0: R_ARGUMENT@23..26 - 0: (empty) - 1: R_DOTS@23..26 - 0: DOTS@23..26 "..." [] [] - 2: R_PAREN@26..27 ")" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@27..41 - 0: R_CALL@27..41 - 0: R_IDENTIFIER@27..30 - 0: IDENT@27..30 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@30..41 - 0: L_PAREN@30..31 "(" [] [] - 1: R_ARGUMENT_LIST@31..40 - 0: R_ARGUMENT@31..32 - 0: (empty) - 1: R_IDENTIFIER@31..32 - 0: IDENT@31..32 "a" [] [] - 1: COMMA@32..34 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@34..37 - 0: (empty) - 1: R_DOTS@34..37 - 0: DOTS@34..37 "..." [] [] - 3: COMMA@37..39 "," [] [Whitespace(" ")] - 4: R_ARGUMENT@39..40 - 0: (empty) - 1: R_IDENTIFIER@39..40 - 0: IDENT@39..40 "b" [] [] - 2: R_PAREN@40..41 ")" [] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@41..52 - 0: R_CALL@41..52 - 0: R_IDENTIFIER@41..45 - 0: IDENT@41..45 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@45..52 - 0: L_PAREN@45..46 "(" [] [] - 1: R_ARGUMENT_LIST@46..51 - 0: R_ARGUMENT@46..51 - 0: R_ARGUMENT_NAME_CLAUSE@46..50 - 0: R_IDENTIFIER@46..48 - 0: IDENT@46..48 "a" [] [Whitespace(" ")] - 1: EQUAL@48..50 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@50..51 - 0: R_DOUBLE_LITERAL@50..51 "1" [] [] - 2: R_PAREN@51..52 ")" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@52..61 - 0: R_CALL@52..61 - 0: R_IDENTIFIER@52..55 - 0: IDENT@52..55 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@55..61 - 0: L_PAREN@55..56 "(" [] [] - 1: R_ARGUMENT_LIST@56..60 - 0: R_ARGUMENT@56..60 - 0: R_ARGUMENT_NAME_CLAUSE@56..60 - 0: R_IDENTIFIER@56..58 - 0: IDENT@56..58 "a" [] [Whitespace(" ")] - 1: EQUAL@58..60 "=" [] [Whitespace(" ")] - 1: (empty) - 2: R_PAREN@60..61 ")" [] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@61..79 - 0: R_CALL@61..79 - 0: R_IDENTIFIER@61..65 - 0: IDENT@61..65 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@65..79 - 0: L_PAREN@65..66 "(" [] [] - 1: R_ARGUMENT_LIST@66..78 - 0: R_ARGUMENT@66..71 - 0: R_ARGUMENT_NAME_CLAUSE@66..70 - 0: R_IDENTIFIER@66..68 - 0: IDENT@66..68 "a" [] [Whitespace(" ")] - 1: EQUAL@68..70 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@70..71 - 0: R_DOUBLE_LITERAL@70..71 "1" [] [] - 1: COMMA@71..73 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@73..78 - 0: R_ARGUMENT_NAME_CLAUSE@73..77 - 0: R_IDENTIFIER@73..75 - 0: IDENT@73..75 "b" [] [Whitespace(" ")] - 1: EQUAL@75..77 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@77..78 - 0: R_DOUBLE_LITERAL@77..78 "2" [] [] - 2: R_PAREN@78..79 ")" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@79..95 - 0: R_CALL@79..95 - 0: R_IDENTIFIER@79..82 - 0: IDENT@79..82 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@82..95 - 0: L_PAREN@82..83 "(" [] [] - 1: R_ARGUMENT_LIST@83..94 - 0: R_ARGUMENT@83..87 - 0: R_ARGUMENT_NAME_CLAUSE@83..87 - 0: R_IDENTIFIER@83..85 - 0: IDENT@83..85 "a" [] [Whitespace(" ")] - 1: EQUAL@85..87 "=" [] [Whitespace(" ")] - 1: (empty) - 1: COMMA@87..89 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@89..94 - 0: R_ARGUMENT_NAME_CLAUSE@89..93 - 0: R_IDENTIFIER@89..91 - 0: IDENT@89..91 "b" [] [Whitespace(" ")] - 1: EQUAL@91..93 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@93..94 - 0: R_DOUBLE_LITERAL@93..94 "2" [] [] - 2: R_PAREN@94..95 ")" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@95..108 - 0: R_CALL@95..108 - 0: R_IDENTIFIER@95..99 - 0: IDENT@95..99 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@99..108 - 0: L_PAREN@99..100 "(" [] [] - 1: R_ARGUMENT_LIST@100..107 - 0: R_ARGUMENT@100..107 - 0: R_ARGUMENT_NAME_CLAUSE@100..106 - 0: R_DOTS@100..104 - 0: DOTS@100..104 "..." [] [Whitespace(" ")] - 1: EQUAL@104..106 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@106..107 - 0: R_DOUBLE_LITERAL@106..107 "1" [] [] - 2: R_PAREN@107..108 ")" [] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@108..118 - 0: R_CALL@108..118 - 0: R_IDENTIFIER@108..111 - 0: IDENT@108..111 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@111..118 - 0: L_PAREN@111..112 "(" [] [] - 1: R_ARGUMENT_LIST@112..117 - 0: R_ARGUMENT@112..117 - 0: R_ARGUMENT_NAME_CLAUSE@112..117 - 0: R_DOTS@112..116 - 0: DOTS@112..116 "..." [] [Whitespace(" ")] - 1: EQUAL@116..117 "=" [] [] - 1: (empty) - 2: R_PAREN@117..118 ")" [] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@118..131 - 0: R_CALL@118..131 - 0: R_IDENTIFIER@118..122 - 0: IDENT@118..122 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@122..131 - 0: L_PAREN@122..123 "(" [] [] - 1: R_ARGUMENT_LIST@123..130 - 0: R_ARGUMENT@123..130 - 0: R_ARGUMENT_NAME_CLAUSE@123..129 - 0: R_DOT_DOT_I@123..127 - 0: DOTDOTI@123..127 "..1" [] [Whitespace(" ")] - 1: EQUAL@127..129 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@129..130 - 0: R_DOUBLE_LITERAL@129..130 "2" [] [] - 2: R_PAREN@130..131 ")" [] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@131..141 - 0: R_CALL@131..141 - 0: R_IDENTIFIER@131..134 - 0: IDENT@131..134 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@134..141 - 0: L_PAREN@134..135 "(" [] [] - 1: R_ARGUMENT_LIST@135..140 - 0: R_ARGUMENT@135..140 - 0: R_ARGUMENT_NAME_CLAUSE@135..140 - 0: R_DOT_DOT_I@135..139 - 0: DOTDOTI@135..139 "..1" [] [Whitespace(" ")] - 1: EQUAL@139..140 "=" [] [] - 1: (empty) - 2: R_PAREN@140..141 ")" [] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@141..156 - 0: R_CALL@141..156 - 0: R_IDENTIFIER@141..145 - 0: IDENT@141..145 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@145..156 - 0: L_PAREN@145..146 "(" [] [] - 1: R_ARGUMENT_LIST@146..155 - 0: R_ARGUMENT@146..155 - 0: R_ARGUMENT_NAME_CLAUSE@146..154 - 0: R_STRING_VALUE@146..152 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@146..152 "\"arg\"" [] [Whitespace(" ")] - 1: EQUAL@152..154 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@154..155 - 0: R_DOUBLE_LITERAL@154..155 "1" [] [] - 2: R_PAREN@155..156 ")" [] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@156..168 - 0: R_CALL@156..168 - 0: R_IDENTIFIER@156..159 - 0: IDENT@156..159 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@159..168 - 0: L_PAREN@159..160 "(" [] [] - 1: R_ARGUMENT_LIST@160..167 - 0: R_ARGUMENT@160..167 - 0: R_ARGUMENT_NAME_CLAUSE@160..167 - 0: R_STRING_VALUE@160..166 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@160..166 "\"arg\"" [] [Whitespace(" ")] - 1: EQUAL@166..167 "=" [] [] - 1: (empty) - 2: R_PAREN@167..168 ")" [] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@168..189 - 0: R_CALL@168..189 - 0: R_IDENTIFIER@168..186 - 0: IDENT@168..186 "fn" [Newline("\n"), Newline("\n"), Comments("# Comma tests"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@186..189 - 0: L_PAREN@186..187 "(" [] [] - 1: R_ARGUMENT_LIST@187..188 - 0: R_ARGUMENT@187..187 - 0: (empty) - 1: (empty) - 1: COMMA@187..188 "," [] [] - 2: R_ARGUMENT@188..188 - 0: (empty) - 1: (empty) - 2: R_PAREN@188..189 ")" [] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@189..197 - 0: R_CALL@189..197 - 0: R_IDENTIFIER@189..192 - 0: IDENT@189..192 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@192..197 - 0: L_PAREN@192..193 "(" [] [] - 1: R_ARGUMENT_LIST@193..196 - 0: R_ARGUMENT@193..193 - 0: (empty) - 1: (empty) - 1: COMMA@193..194 "," [] [] - 2: R_ARGUMENT@194..194 - 0: (empty) - 1: (empty) - 3: COMMA@194..195 "," [] [] - 4: R_ARGUMENT@195..195 - 0: (empty) - 1: (empty) - 5: COMMA@195..196 "," [] [] - 6: R_ARGUMENT@196..196 - 0: (empty) - 1: (empty) - 2: R_PAREN@196..197 ")" [] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@197..210 - 0: R_CALL@197..210 - 0: R_IDENTIFIER@197..200 - 0: IDENT@197..200 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@200..210 - 0: L_PAREN@200..201 "(" [] [] - 1: R_ARGUMENT_LIST@201..209 - 0: R_ARGUMENT@201..201 - 0: (empty) - 1: (empty) - 1: COMMA@201..202 "," [] [] - 2: R_ARGUMENT@202..202 - 0: (empty) - 1: (empty) - 3: COMMA@202..203 "," [] [] - 4: R_ARGUMENT@203..204 - 0: (empty) - 1: R_IDENTIFIER@203..204 - 0: IDENT@203..204 "a" [] [] - 5: COMMA@204..205 "," [] [] - 6: R_ARGUMENT@205..205 - 0: (empty) - 1: (empty) - 7: COMMA@205..206 "," [] [] - 8: R_ARGUMENT@206..207 - 0: (empty) - 1: R_IDENTIFIER@206..207 - 0: IDENT@206..207 "b" [] [] - 9: COMMA@207..208 "," [] [] - 10: R_ARGUMENT@208..208 - 0: (empty) - 1: (empty) - 11: COMMA@208..209 "," [] [] - 12: R_ARGUMENT@209..209 - 0: (empty) - 1: (empty) - 2: R_PAREN@209..210 ")" [] [] - 1: (empty) - 18: R_EXPRESSION_STATEMENT@210..245 - 0: R_CALL@210..245 - 0: R_IDENTIFIER@210..230 - 0: IDENT@210..230 "fn" [Newline("\n"), Newline("\n"), Comments("# Comment tests"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@230..245 - 0: L_PAREN@230..231 "(" [] [] - 1: R_ARGUMENT_LIST@231..231 - 2: R_PAREN@231..245 ")" [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@245..265 - 0: R_CALL@245..265 - 0: R_IDENTIFIER@245..248 - 0: IDENT@245..248 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@248..265 - 0: L_PAREN@248..249 "(" [] [] - 1: R_ARGUMENT_LIST@249..264 - 0: R_ARGUMENT@249..249 - 0: (empty) - 1: (empty) - 1: COMMA@249..250 "," [] [] - 2: R_ARGUMENT@250..250 - 0: (empty) - 1: (empty) - 3: COMMA@250..264 "," [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [] - 4: R_ARGUMENT@264..264 - 0: (empty) - 1: (empty) - 2: R_PAREN@264..265 ")" [] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@265..302 - 0: R_CALL@265..302 - 0: R_IDENTIFIER@265..268 - 0: IDENT@265..268 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@268..302 - 0: L_PAREN@268..269 "(" [] [] - 1: R_ARGUMENT_LIST@269..300 - 0: R_ARGUMENT@269..273 - 0: (empty) - 1: R_IDENTIFIER@269..273 - 0: IDENT@269..273 "a" [Newline("\n"), Whitespace(" ")] [] - 1: COMMA@273..285 "," [] [Whitespace(" "), Comments("# comment1")] - 2: R_ARGUMENT@285..300 - 0: (empty) - 1: R_IDENTIFIER@285..300 - 0: IDENT@285..300 "b" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")] - 2: R_PAREN@300..302 ")" [Newline("\n")] [] - 1: (empty) - 21: R_EXPRESSION_STATEMENT@302..353 - 0: R_CALL@302..353 - 0: R_IDENTIFIER@302..305 - 0: IDENT@302..305 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@305..353 - 0: L_PAREN@305..306 "(" [] [] - 1: R_ARGUMENT_LIST@306..351 - 0: R_ARGUMENT@306..351 - 0: R_ARGUMENT_NAME_CLAUSE@306..336 - 0: R_IDENTIFIER@306..321 - 0: IDENT@306..321 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment1")] - 1: EQUAL@321..336 "=" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")] - 1: R_DOUBLE_VALUE@336..351 - 0: R_DOUBLE_LITERAL@336..351 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")] - 2: R_PAREN@351..353 ")" [Newline("\n")] [] - 1: (empty) - 22: R_EXPRESSION_STATEMENT@353..368 - 0: R_CALL@353..368 - 0: R_BRACED_STATEMENTS@353..361 - 0: L_CURLY@353..356 "{" [Newline("\n"), Newline("\n")] [] - 1: R_STATEMENT_LIST@356..360 - 0: R_EXPRESSION_STATEMENT@356..360 - 0: R_IDENTIFIER@356..360 - 0: IDENT@356..360 "expr" [] [] - 1: (empty) - 2: R_CURLY@360..361 "}" [] [] - 1: R_CALL_ARGUMENTS@361..368 - 0: L_PAREN@361..362 "(" [] [] - 1: R_ARGUMENT_LIST@362..367 - 0: R_ARGUMENT@362..367 - 0: R_ARGUMENT_NAME_CLAUSE@362..366 - 0: R_IDENTIFIER@362..364 - 0: IDENT@362..364 "a" [] [Whitespace(" ")] - 1: EQUAL@364..366 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@366..367 - 0: R_DOUBLE_LITERAL@366..367 "1" [] [] - 2: R_PAREN@367..368 ")" [] [] - 1: (empty) - 23: R_EXPRESSION_STATEMENT@368..435 - 0: R_CALL@368..435 - 0: R_IDENTIFIER@368..421 - 0: IDENT@368..421 "fn" [Newline("\n"), Newline("\n"), Comments("# `NULL` argument nam ..."), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@421..435 - 0: L_PAREN@421..422 "(" [] [] - 1: R_ARGUMENT_LIST@422..433 - 0: R_ARGUMENT@422..433 - 0: R_ARGUMENT_NAME_CLAUSE@422..432 - 0: R_NULL_EXPRESSION@422..430 - 0: NULL_KW@422..430 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: EQUAL@430..432 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@432..433 - 0: R_DOUBLE_LITERAL@432..433 "1" [] [] - 2: R_PAREN@433..435 ")" [Newline("\n")] [] - 1: (empty) - 24: R_EXPRESSION_STATEMENT@435..451 - 0: R_CALL@435..451 - 0: R_IDENTIFIER@435..438 - 0: IDENT@435..438 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@438..451 - 0: L_PAREN@438..439 "(" [] [] - 1: R_ARGUMENT_LIST@439..449 - 0: R_ARGUMENT@439..449 - 0: R_ARGUMENT_NAME_CLAUSE@439..449 - 0: R_NULL_EXPRESSION@439..447 - 0: NULL_KW@439..447 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: EQUAL@447..449 "=" [] [Whitespace(" ")] - 1: (empty) - 2: R_PAREN@449..451 ")" [Newline("\n")] [] - 1: (empty) - 25: R_EXPRESSION_STATEMENT@451..468 - 0: R_CALL@451..468 - 0: R_IDENTIFIER@451..454 - 0: IDENT@451..454 "fn" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@454..468 - 0: L_PAREN@454..455 "(" [] [] - 1: R_ARGUMENT_LIST@455..466 - 0: R_ARGUMENT@455..465 - 0: R_ARGUMENT_NAME_CLAUSE@455..465 - 0: R_NULL_EXPRESSION@455..463 - 0: NULL_KW@455..463 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: EQUAL@463..465 "=" [] [Whitespace(" ")] - 1: (empty) - 1: COMMA@465..466 "," [] [] - 2: R_ARGUMENT@466..466 - 0: (empty) - 1: (empty) - 2: R_PAREN@466..468 ")" [Newline("\n")] [] - 1: (empty) - 26: R_EXPRESSION_STATEMENT@468..548 - 0: R_CALL@468..548 - 0: R_IDENTIFIER@468..546 - 0: IDENT@468..546 "return" [Newline("\n"), Newline("\n"), Comments("# Standard call as of ..."), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@546..548 - 0: L_PAREN@546..547 "(" [] [] - 1: R_ARGUMENT_LIST@547..547 - 2: R_PAREN@547..548 ")" [] [] - 1: (empty) - 2: EOF@548..548 "" [] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/comments.R b/crates/air_r_parser/tests/snapshots/ok/comments.R deleted file mode 100644 index 9ed0cb0d..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/comments.R +++ /dev/null @@ -1,3 +0,0 @@ -# Leading -1 + 1 # Trailing -# Leading diff --git a/crates/air_r_parser/tests/snapshots/ok/comments.R.snap b/crates/air_r_parser/tests/snapshots/ok/comments.R.snap deleted file mode 100644 index a3159931..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/comments.R.snap +++ /dev/null @@ -1,54 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -# Leading -1 + 1 # Trailing -# Leading - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBinaryExpression { - left: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..12 "1" [Comments("# Leading"), Newline("\n")] [Whitespace(" ")], - }, - operator: PLUS@12..14 "+" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@14..26 "1" [] [Whitespace(" "), Comments("# Trailing")], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@26..37 "" [Newline("\n"), Comments("# Leading"), Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..37 - 0: (empty) - 1: R_STATEMENT_LIST@0..26 - 0: R_EXPRESSION_STATEMENT@0..26 - 0: R_BINARY_EXPRESSION@0..26 - 0: R_DOUBLE_VALUE@0..12 - 0: R_DOUBLE_LITERAL@0..12 "1" [Comments("# Leading"), Newline("\n")] [Whitespace(" ")] - 1: PLUS@12..14 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@14..26 - 0: R_DOUBLE_LITERAL@14..26 "1" [] [Whitespace(" "), Comments("# Trailing")] - 1: (empty) - 2: EOF@26..37 "" [Newline("\n"), Comments("# Leading"), Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/crlf/multiline_string_value.R b/crates/air_r_parser/tests/snapshots/ok/crlf/multiline_string_value.R deleted file mode 100644 index 198f3f21..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/crlf/multiline_string_value.R +++ /dev/null @@ -1,2 +0,0 @@ -"multiline -string" diff --git a/crates/air_r_parser/tests/snapshots/ok/crlf/multiline_string_value.R.snap b/crates/air_r_parser/tests/snapshots/ok/crlf/multiline_string_value.R.snap deleted file mode 100644 index a52f18f7..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/crlf/multiline_string_value.R.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -"multiline -string" - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@0..19 "\"multiline\r\nstring\"" [] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@19..21 "" [Newline("\r\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..21 - 0: (empty) - 1: R_STATEMENT_LIST@0..19 - 0: R_EXPRESSION_STATEMENT@0..19 - 0: R_STRING_VALUE@0..19 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@0..19 "\"multiline\r\nstring\"" [] [] - 1: (empty) - 2: EOF@19..21 "" [Newline("\r\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/crlf/trailing_comments.R b/crates/air_r_parser/tests/snapshots/ok/crlf/trailing_comments.R deleted file mode 100644 index 9ed0cb0d..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/crlf/trailing_comments.R +++ /dev/null @@ -1,3 +0,0 @@ -# Leading -1 + 1 # Trailing -# Leading diff --git a/crates/air_r_parser/tests/snapshots/ok/crlf/trailing_comments.R.snap b/crates/air_r_parser/tests/snapshots/ok/crlf/trailing_comments.R.snap deleted file mode 100644 index 52fa8bf2..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/crlf/trailing_comments.R.snap +++ /dev/null @@ -1,54 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -# Leading -1 + 1 # Trailing -# Leading - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RBinaryExpression { - left: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..13 "1" [Comments("# Leading"), Newline("\r\n")] [Whitespace(" ")], - }, - operator: PLUS@13..15 "+" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@15..27 "1" [] [Whitespace(" "), Comments("# Trailing")], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@27..40 "" [Newline("\r\n"), Comments("# Leading"), Newline("\r\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..40 - 0: (empty) - 1: R_STATEMENT_LIST@0..27 - 0: R_EXPRESSION_STATEMENT@0..27 - 0: R_BINARY_EXPRESSION@0..27 - 0: R_DOUBLE_VALUE@0..13 - 0: R_DOUBLE_LITERAL@0..13 "1" [Comments("# Leading"), Newline("\r\n")] [Whitespace(" ")] - 1: PLUS@13..15 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@15..27 - 0: R_DOUBLE_LITERAL@15..27 "1" [] [Whitespace(" "), Comments("# Trailing")] - 1: (empty) - 2: EOF@27..40 "" [Newline("\r\n"), Comments("# Leading"), Newline("\r\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/dot_dot_i.R b/crates/air_r_parser/tests/snapshots/ok/dot_dot_i.R deleted file mode 100644 index 8a0b30ec..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/dot_dot_i.R +++ /dev/null @@ -1,10 +0,0 @@ -..1 -..2 -..10 - -function(...) { - ..1 + ..2 -} - -# Parses as a function -..1() diff --git a/crates/air_r_parser/tests/snapshots/ok/dot_dot_i.R.snap b/crates/air_r_parser/tests/snapshots/ok/dot_dot_i.R.snap deleted file mode 100644 index d241a295..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/dot_dot_i.R.snap +++ /dev/null @@ -1,153 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -..1 -..2 -..10 - -function(...) { - ..1 + ..2 -} - -# Parses as a function -..1() - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDotDotI { - value_token: DOTDOTI@0..3 "..1" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDotDotI { - value_token: DOTDOTI@3..7 "..2" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDotDotI { - value_token: DOTDOTI@7..12 "..10" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@12..22 "function" [Newline("\n"), Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@22..23 "(" [] [], - items: RParameterList [ - RParameter { - name: RDots { - value_token: DOTS@23..26 "..." [] [], - }, - default: missing (optional), - }, - ], - r_paren_token: R_PAREN@26..28 ")" [] [Whitespace(" ")], - }, - body: RBracedStatements { - l_curly_token: L_CURLY@28..29 "{" [] [], - statements: RStatementList [ - RExpressionStatement { - expression: RBinaryExpression { - left: RDotDotI { - value_token: DOTDOTI@29..36 "..1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: PLUS@36..38 "+" [] [Whitespace(" ")], - right: RDotDotI { - value_token: DOTDOTI@38..41 "..2" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@41..43 "}" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RDotDotI { - value_token: DOTDOTI@43..71 "..1" [Newline("\n"), Newline("\n"), Comments("# Parses as a function"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@71..72 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@72..73 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@73..74 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..74 - 0: (empty) - 1: R_STATEMENT_LIST@0..73 - 0: R_EXPRESSION_STATEMENT@0..3 - 0: R_DOT_DOT_I@0..3 - 0: DOTDOTI@0..3 "..1" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@3..7 - 0: R_DOT_DOT_I@3..7 - 0: DOTDOTI@3..7 "..2" [Newline("\n")] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@7..12 - 0: R_DOT_DOT_I@7..12 - 0: DOTDOTI@7..12 "..10" [Newline("\n")] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@12..43 - 0: R_FUNCTION_DEFINITION@12..43 - 0: FUNCTION_KW@12..22 "function" [Newline("\n"), Newline("\n")] [] - 1: R_PARAMETERS@22..28 - 0: L_PAREN@22..23 "(" [] [] - 1: R_PARAMETER_LIST@23..26 - 0: R_PARAMETER@23..26 - 0: R_DOTS@23..26 - 0: DOTS@23..26 "..." [] [] - 1: (empty) - 2: R_PAREN@26..28 ")" [] [Whitespace(" ")] - 2: R_BRACED_STATEMENTS@28..43 - 0: L_CURLY@28..29 "{" [] [] - 1: R_STATEMENT_LIST@29..41 - 0: R_EXPRESSION_STATEMENT@29..41 - 0: R_BINARY_EXPRESSION@29..41 - 0: R_DOT_DOT_I@29..36 - 0: DOTDOTI@29..36 "..1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: PLUS@36..38 "+" [] [Whitespace(" ")] - 2: R_DOT_DOT_I@38..41 - 0: DOTDOTI@38..41 "..2" [] [] - 1: (empty) - 2: R_CURLY@41..43 "}" [Newline("\n")] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@43..73 - 0: R_CALL@43..73 - 0: R_DOT_DOT_I@43..71 - 0: DOTDOTI@43..71 "..1" [Newline("\n"), Newline("\n"), Comments("# Parses as a function"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@71..73 - 0: L_PAREN@71..72 "(" [] [] - 1: R_ARGUMENT_LIST@72..72 - 2: R_PAREN@72..73 ")" [] [] - 1: (empty) - 2: EOF@73..74 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/dots.R b/crates/air_r_parser/tests/snapshots/ok/dots.R deleted file mode 100644 index 77d7b3c8..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/dots.R +++ /dev/null @@ -1,7 +0,0 @@ -# Parses, but fails at runtime -... - -{ ... } - -# An actual base R function -...() diff --git a/crates/air_r_parser/tests/snapshots/ok/dots.R.snap b/crates/air_r_parser/tests/snapshots/ok/dots.R.snap deleted file mode 100644 index 58fc50bd..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/dots.R.snap +++ /dev/null @@ -1,95 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -# Parses, but fails at runtime -... - -{ ... } - -# An actual base R function -...() - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDots { - value_token: DOTS@0..34 "..." [Comments("# Parses, but fails a ..."), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@34..38 "{" [Newline("\n"), Newline("\n")] [Whitespace(" ")], - statements: RStatementList [ - RExpressionStatement { - expression: RDots { - value_token: DOTS@38..42 "..." [] [Whitespace(" ")], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@42..43 "}" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RDots { - value_token: DOTS@43..76 "..." [Newline("\n"), Newline("\n"), Comments("# An actual base R fu ..."), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@76..77 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@77..78 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@78..79 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..79 - 0: (empty) - 1: R_STATEMENT_LIST@0..78 - 0: R_EXPRESSION_STATEMENT@0..34 - 0: R_DOTS@0..34 - 0: DOTS@0..34 "..." [Comments("# Parses, but fails a ..."), Newline("\n")] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@34..43 - 0: R_BRACED_STATEMENTS@34..43 - 0: L_CURLY@34..38 "{" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: R_STATEMENT_LIST@38..42 - 0: R_EXPRESSION_STATEMENT@38..42 - 0: R_DOTS@38..42 - 0: DOTS@38..42 "..." [] [Whitespace(" ")] - 1: (empty) - 2: R_CURLY@42..43 "}" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@43..78 - 0: R_CALL@43..78 - 0: R_DOTS@43..76 - 0: DOTS@43..76 "..." [Newline("\n"), Newline("\n"), Comments("# An actual base R fu ..."), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@76..78 - 0: L_PAREN@76..77 "(" [] [] - 1: R_ARGUMENT_LIST@77..77 - 2: R_PAREN@77..78 ")" [] [] - 1: (empty) - 2: EOF@78..79 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/extract_expression.R b/crates/air_r_parser/tests/snapshots/ok/extract_expression.R deleted file mode 100644 index 87796fe4..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/extract_expression.R +++ /dev/null @@ -1,26 +0,0 @@ -a$b -a@b - -a$`b` -a@`b` - -a$"b" -a@"b" - -a()$b -a()@b - -a$b$c -a@b@c - -a$b() -a@b() - -a$... -a@... - -a$..1 -a@..1 - -a$..10 -a@..10 diff --git a/crates/air_r_parser/tests/snapshots/ok/extract_expression.R.snap b/crates/air_r_parser/tests/snapshots/ok/extract_expression.R.snap deleted file mode 100644 index 7a01d851..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/extract_expression.R.snap +++ /dev/null @@ -1,485 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -a$b -a@b - -a$`b` -a@`b` - -a$"b" -a@"b" - -a()$b -a()@b - -a$b$c -a@b@c - -a$b() -a@b() - -a$... -a@... - -a$..1 -a@..1 - -a$..10 -a@..10 - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@0..1 "a" [] [], - }, - operator: DOLLAR@1..2 "$" [] [], - right: RIdentifier { - name_token: IDENT@2..3 "b" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@3..5 "a" [Newline("\n")] [], - }, - operator: AT@5..6 "@" [] [], - right: RIdentifier { - name_token: IDENT@6..7 "b" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@7..10 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: DOLLAR@10..11 "$" [] [], - right: RIdentifier { - name_token: IDENT@11..14 "`b`" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@14..16 "a" [Newline("\n")] [], - }, - operator: AT@16..17 "@" [] [], - right: RIdentifier { - name_token: IDENT@17..20 "`b`" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@20..23 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: DOLLAR@23..24 "$" [] [], - right: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@24..27 "\"b\"" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@27..29 "a" [Newline("\n")] [], - }, - operator: AT@29..30 "@" [] [], - right: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@30..33 "\"b\"" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RCall { - function: RIdentifier { - name_token: IDENT@33..36 "a" [Newline("\n"), Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@36..37 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@37..38 ")" [] [], - }, - }, - operator: DOLLAR@38..39 "$" [] [], - right: RIdentifier { - name_token: IDENT@39..40 "b" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RCall { - function: RIdentifier { - name_token: IDENT@40..42 "a" [Newline("\n")] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@42..43 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@43..44 ")" [] [], - }, - }, - operator: AT@44..45 "@" [] [], - right: RIdentifier { - name_token: IDENT@45..46 "b" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RExtractExpression { - left: RIdentifier { - name_token: IDENT@46..49 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: DOLLAR@49..50 "$" [] [], - right: RIdentifier { - name_token: IDENT@50..51 "b" [] [], - }, - }, - operator: DOLLAR@51..52 "$" [] [], - right: RIdentifier { - name_token: IDENT@52..53 "c" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RExtractExpression { - left: RIdentifier { - name_token: IDENT@53..55 "a" [Newline("\n")] [], - }, - operator: AT@55..56 "@" [] [], - right: RIdentifier { - name_token: IDENT@56..57 "b" [] [], - }, - }, - operator: AT@57..58 "@" [] [], - right: RIdentifier { - name_token: IDENT@58..59 "c" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RExtractExpression { - left: RIdentifier { - name_token: IDENT@59..62 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: DOLLAR@62..63 "$" [] [], - right: RIdentifier { - name_token: IDENT@63..64 "b" [] [], - }, - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@64..65 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@65..66 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RExtractExpression { - left: RIdentifier { - name_token: IDENT@66..68 "a" [Newline("\n")] [], - }, - operator: AT@68..69 "@" [] [], - right: RIdentifier { - name_token: IDENT@69..70 "b" [] [], - }, - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@70..71 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@71..72 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@72..75 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: DOLLAR@75..76 "$" [] [], - right: RDots { - value_token: DOTS@76..79 "..." [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@79..81 "a" [Newline("\n")] [], - }, - operator: AT@81..82 "@" [] [], - right: RDots { - value_token: DOTS@82..85 "..." [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@85..88 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: DOLLAR@88..89 "$" [] [], - right: RDotDotI { - value_token: DOTDOTI@89..92 "..1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@92..94 "a" [Newline("\n")] [], - }, - operator: AT@94..95 "@" [] [], - right: RDotDotI { - value_token: DOTDOTI@95..98 "..1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@98..101 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: DOLLAR@101..102 "$" [] [], - right: RDotDotI { - value_token: DOTDOTI@102..106 "..10" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@106..108 "a" [Newline("\n")] [], - }, - operator: AT@108..109 "@" [] [], - right: RDotDotI { - value_token: DOTDOTI@109..113 "..10" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@113..114 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..114 - 0: (empty) - 1: R_STATEMENT_LIST@0..113 - 0: R_EXPRESSION_STATEMENT@0..3 - 0: R_EXTRACT_EXPRESSION@0..3 - 0: R_IDENTIFIER@0..1 - 0: IDENT@0..1 "a" [] [] - 1: DOLLAR@1..2 "$" [] [] - 2: R_IDENTIFIER@2..3 - 0: IDENT@2..3 "b" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@3..7 - 0: R_EXTRACT_EXPRESSION@3..7 - 0: R_IDENTIFIER@3..5 - 0: IDENT@3..5 "a" [Newline("\n")] [] - 1: AT@5..6 "@" [] [] - 2: R_IDENTIFIER@6..7 - 0: IDENT@6..7 "b" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@7..14 - 0: R_EXTRACT_EXPRESSION@7..14 - 0: R_IDENTIFIER@7..10 - 0: IDENT@7..10 "a" [Newline("\n"), Newline("\n")] [] - 1: DOLLAR@10..11 "$" [] [] - 2: R_IDENTIFIER@11..14 - 0: IDENT@11..14 "`b`" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@14..20 - 0: R_EXTRACT_EXPRESSION@14..20 - 0: R_IDENTIFIER@14..16 - 0: IDENT@14..16 "a" [Newline("\n")] [] - 1: AT@16..17 "@" [] [] - 2: R_IDENTIFIER@17..20 - 0: IDENT@17..20 "`b`" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@20..27 - 0: R_EXTRACT_EXPRESSION@20..27 - 0: R_IDENTIFIER@20..23 - 0: IDENT@20..23 "a" [Newline("\n"), Newline("\n")] [] - 1: DOLLAR@23..24 "$" [] [] - 2: R_STRING_VALUE@24..27 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@24..27 "\"b\"" [] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@27..33 - 0: R_EXTRACT_EXPRESSION@27..33 - 0: R_IDENTIFIER@27..29 - 0: IDENT@27..29 "a" [Newline("\n")] [] - 1: AT@29..30 "@" [] [] - 2: R_STRING_VALUE@30..33 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@30..33 "\"b\"" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@33..40 - 0: R_EXTRACT_EXPRESSION@33..40 - 0: R_CALL@33..38 - 0: R_IDENTIFIER@33..36 - 0: IDENT@33..36 "a" [Newline("\n"), Newline("\n")] [] - 1: R_CALL_ARGUMENTS@36..38 - 0: L_PAREN@36..37 "(" [] [] - 1: R_ARGUMENT_LIST@37..37 - 2: R_PAREN@37..38 ")" [] [] - 1: DOLLAR@38..39 "$" [] [] - 2: R_IDENTIFIER@39..40 - 0: IDENT@39..40 "b" [] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@40..46 - 0: R_EXTRACT_EXPRESSION@40..46 - 0: R_CALL@40..44 - 0: R_IDENTIFIER@40..42 - 0: IDENT@40..42 "a" [Newline("\n")] [] - 1: R_CALL_ARGUMENTS@42..44 - 0: L_PAREN@42..43 "(" [] [] - 1: R_ARGUMENT_LIST@43..43 - 2: R_PAREN@43..44 ")" [] [] - 1: AT@44..45 "@" [] [] - 2: R_IDENTIFIER@45..46 - 0: IDENT@45..46 "b" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@46..53 - 0: R_EXTRACT_EXPRESSION@46..53 - 0: R_EXTRACT_EXPRESSION@46..51 - 0: R_IDENTIFIER@46..49 - 0: IDENT@46..49 "a" [Newline("\n"), Newline("\n")] [] - 1: DOLLAR@49..50 "$" [] [] - 2: R_IDENTIFIER@50..51 - 0: IDENT@50..51 "b" [] [] - 1: DOLLAR@51..52 "$" [] [] - 2: R_IDENTIFIER@52..53 - 0: IDENT@52..53 "c" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@53..59 - 0: R_EXTRACT_EXPRESSION@53..59 - 0: R_EXTRACT_EXPRESSION@53..57 - 0: R_IDENTIFIER@53..55 - 0: IDENT@53..55 "a" [Newline("\n")] [] - 1: AT@55..56 "@" [] [] - 2: R_IDENTIFIER@56..57 - 0: IDENT@56..57 "b" [] [] - 1: AT@57..58 "@" [] [] - 2: R_IDENTIFIER@58..59 - 0: IDENT@58..59 "c" [] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@59..66 - 0: R_CALL@59..66 - 0: R_EXTRACT_EXPRESSION@59..64 - 0: R_IDENTIFIER@59..62 - 0: IDENT@59..62 "a" [Newline("\n"), Newline("\n")] [] - 1: DOLLAR@62..63 "$" [] [] - 2: R_IDENTIFIER@63..64 - 0: IDENT@63..64 "b" [] [] - 1: R_CALL_ARGUMENTS@64..66 - 0: L_PAREN@64..65 "(" [] [] - 1: R_ARGUMENT_LIST@65..65 - 2: R_PAREN@65..66 ")" [] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@66..72 - 0: R_CALL@66..72 - 0: R_EXTRACT_EXPRESSION@66..70 - 0: R_IDENTIFIER@66..68 - 0: IDENT@66..68 "a" [Newline("\n")] [] - 1: AT@68..69 "@" [] [] - 2: R_IDENTIFIER@69..70 - 0: IDENT@69..70 "b" [] [] - 1: R_CALL_ARGUMENTS@70..72 - 0: L_PAREN@70..71 "(" [] [] - 1: R_ARGUMENT_LIST@71..71 - 2: R_PAREN@71..72 ")" [] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@72..79 - 0: R_EXTRACT_EXPRESSION@72..79 - 0: R_IDENTIFIER@72..75 - 0: IDENT@72..75 "a" [Newline("\n"), Newline("\n")] [] - 1: DOLLAR@75..76 "$" [] [] - 2: R_DOTS@76..79 - 0: DOTS@76..79 "..." [] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@79..85 - 0: R_EXTRACT_EXPRESSION@79..85 - 0: R_IDENTIFIER@79..81 - 0: IDENT@79..81 "a" [Newline("\n")] [] - 1: AT@81..82 "@" [] [] - 2: R_DOTS@82..85 - 0: DOTS@82..85 "..." [] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@85..92 - 0: R_EXTRACT_EXPRESSION@85..92 - 0: R_IDENTIFIER@85..88 - 0: IDENT@85..88 "a" [Newline("\n"), Newline("\n")] [] - 1: DOLLAR@88..89 "$" [] [] - 2: R_DOT_DOT_I@89..92 - 0: DOTDOTI@89..92 "..1" [] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@92..98 - 0: R_EXTRACT_EXPRESSION@92..98 - 0: R_IDENTIFIER@92..94 - 0: IDENT@92..94 "a" [Newline("\n")] [] - 1: AT@94..95 "@" [] [] - 2: R_DOT_DOT_I@95..98 - 0: DOTDOTI@95..98 "..1" [] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@98..106 - 0: R_EXTRACT_EXPRESSION@98..106 - 0: R_IDENTIFIER@98..101 - 0: IDENT@98..101 "a" [Newline("\n"), Newline("\n")] [] - 1: DOLLAR@101..102 "$" [] [] - 2: R_DOT_DOT_I@102..106 - 0: DOTDOTI@102..106 "..10" [] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@106..113 - 0: R_EXTRACT_EXPRESSION@106..113 - 0: R_IDENTIFIER@106..108 - 0: IDENT@106..108 "a" [Newline("\n")] [] - 1: AT@108..109 "@" [] [] - 2: R_DOT_DOT_I@109..113 - 0: DOTDOTI@109..113 "..10" [] [] - 1: (empty) - 2: EOF@113..114 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/for_statement.R b/crates/air_r_parser/tests/snapshots/ok/for_statement.R deleted file mode 100644 index 51c9caa6..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/for_statement.R +++ /dev/null @@ -1,13 +0,0 @@ -for (i in 1) i - -for ( - i # comment - in # comment2 - 1 # comment3 -) i - -for # dangling1 - ( # dangling2 - a in 1 - ) # dangling3 - a diff --git a/crates/air_r_parser/tests/snapshots/ok/for_statement.R.snap b/crates/air_r_parser/tests/snapshots/ok/for_statement.R.snap deleted file mode 100644 index 40e99dfa..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/for_statement.R.snap +++ /dev/null @@ -1,137 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -for (i in 1) i - -for ( - i # comment - in # comment2 - 1 # comment3 -) i - -for # dangling1 - ( # dangling2 - a in 1 - ) # dangling3 - a - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RForStatement { - for_token: FOR_KW@0..4 "for" [] [Whitespace(" ")], - l_paren_token: L_PAREN@4..5 "(" [] [], - variable: RIdentifier { - name_token: IDENT@5..7 "i" [] [Whitespace(" ")], - }, - in_token: IN_KW@7..10 "in" [] [Whitespace(" ")], - sequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@10..11 "1" [] [], - }, - r_paren_token: R_PAREN@11..13 ")" [] [Whitespace(" ")], - body: RIdentifier { - name_token: IDENT@13..14 "i" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RForStatement { - for_token: FOR_KW@14..20 "for" [Newline("\n"), Newline("\n")] [Whitespace(" ")], - l_paren_token: L_PAREN@20..21 "(" [] [], - variable: RIdentifier { - name_token: IDENT@21..35 "i" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment")], - }, - in_token: IN_KW@35..51 "in" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")], - sequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@51..66 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")], - }, - r_paren_token: R_PAREN@66..69 ")" [Newline("\n")] [Whitespace(" ")], - body: RIdentifier { - name_token: IDENT@69..70 "i" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RForStatement { - for_token: FOR_KW@70..87 "for" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# dangling1")], - l_paren_token: L_PAREN@87..103 "(" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# dangling2")], - variable: RIdentifier { - name_token: IDENT@103..110 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - in_token: IN_KW@110..113 "in" [] [Whitespace(" ")], - sequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@113..114 "1" [] [], - }, - r_paren_token: R_PAREN@114..130 ")" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# dangling3")], - body: RIdentifier { - name_token: IDENT@130..134 "a" [Newline("\n"), Whitespace(" ")] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@134..135 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..135 - 0: (empty) - 1: R_STATEMENT_LIST@0..134 - 0: R_EXPRESSION_STATEMENT@0..14 - 0: R_FOR_STATEMENT@0..14 - 0: FOR_KW@0..4 "for" [] [Whitespace(" ")] - 1: L_PAREN@4..5 "(" [] [] - 2: R_IDENTIFIER@5..7 - 0: IDENT@5..7 "i" [] [Whitespace(" ")] - 3: IN_KW@7..10 "in" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@10..11 - 0: R_DOUBLE_LITERAL@10..11 "1" [] [] - 5: R_PAREN@11..13 ")" [] [Whitespace(" ")] - 6: R_IDENTIFIER@13..14 - 0: IDENT@13..14 "i" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@14..70 - 0: R_FOR_STATEMENT@14..70 - 0: FOR_KW@14..20 "for" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: L_PAREN@20..21 "(" [] [] - 2: R_IDENTIFIER@21..35 - 0: IDENT@21..35 "i" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment")] - 3: IN_KW@35..51 "in" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")] - 4: R_DOUBLE_VALUE@51..66 - 0: R_DOUBLE_LITERAL@51..66 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")] - 5: R_PAREN@66..69 ")" [Newline("\n")] [Whitespace(" ")] - 6: R_IDENTIFIER@69..70 - 0: IDENT@69..70 "i" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@70..134 - 0: R_FOR_STATEMENT@70..134 - 0: FOR_KW@70..87 "for" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# dangling1")] - 1: L_PAREN@87..103 "(" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# dangling2")] - 2: R_IDENTIFIER@103..110 - 0: IDENT@103..110 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 3: IN_KW@110..113 "in" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@113..114 - 0: R_DOUBLE_LITERAL@113..114 "1" [] [] - 5: R_PAREN@114..130 ")" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# dangling3")] - 6: R_IDENTIFIER@130..134 - 0: IDENT@130..134 "a" [Newline("\n"), Whitespace(" ")] [] - 1: (empty) - 2: EOF@134..135 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/function_definition.R b/crates/air_r_parser/tests/snapshots/ok/function_definition.R deleted file mode 100644 index eef66550..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/function_definition.R +++ /dev/null @@ -1,17 +0,0 @@ -function() 1 -function(a) a -function(a, b) a + b -function( - a # important! -) a -function(...) 1 -function(a, ..., b) 1 -function(a = 1, ..., b = 2) a -function(..1, ..2) get("..1") + get("..2") -function(..1 = 1, ..2 = 2) get("..1") + get("..2") -function( - x = # important! - 4 -) 1 - -\(x, y) 1 diff --git a/crates/air_r_parser/tests/snapshots/ok/function_definition.R.snap b/crates/air_r_parser/tests/snapshots/ok/function_definition.R.snap deleted file mode 100644 index aefa6ef9..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/function_definition.R.snap +++ /dev/null @@ -1,680 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -function() 1 -function(a) a -function(a, b) a + b -function( - a # important! -) a -function(...) 1 -function(a, ..., b) 1 -function(a = 1, ..., b = 2) a -function(..1, ..2) get("..1") + get("..2") -function(..1 = 1, ..2 = 2) get("..1") + get("..2") -function( - x = # important! - 4 -) 1 - -\(x, y) 1 - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@0..8 "function" [] [], - parameters: RParameters { - l_paren_token: L_PAREN@8..9 "(" [] [], - items: RParameterList [], - r_paren_token: R_PAREN@9..11 ")" [] [Whitespace(" ")], - }, - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@11..12 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@12..21 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@21..22 "(" [] [], - items: RParameterList [ - RParameter { - name: RIdentifier { - name_token: IDENT@22..23 "a" [] [], - }, - default: missing (optional), - }, - ], - r_paren_token: R_PAREN@23..25 ")" [] [Whitespace(" ")], - }, - body: RIdentifier { - name_token: IDENT@25..26 "a" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@26..35 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@35..36 "(" [] [], - items: RParameterList [ - RParameter { - name: RIdentifier { - name_token: IDENT@36..37 "a" [] [], - }, - default: missing (optional), - }, - COMMA@37..39 "," [] [Whitespace(" ")], - RParameter { - name: RIdentifier { - name_token: IDENT@39..40 "b" [] [], - }, - default: missing (optional), - }, - ], - r_paren_token: R_PAREN@40..42 ")" [] [Whitespace(" ")], - }, - body: RBinaryExpression { - left: RIdentifier { - name_token: IDENT@42..44 "a" [] [Whitespace(" ")], - }, - operator: PLUS@44..46 "+" [] [Whitespace(" ")], - right: RIdentifier { - name_token: IDENT@46..47 "b" [] [], - }, - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@47..56 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@56..57 "(" [] [], - items: RParameterList [ - RParameter { - name: RIdentifier { - name_token: IDENT@57..74 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# important!")], - }, - default: missing (optional), - }, - ], - r_paren_token: R_PAREN@74..77 ")" [Newline("\n")] [Whitespace(" ")], - }, - body: RIdentifier { - name_token: IDENT@77..78 "a" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@78..87 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@87..88 "(" [] [], - items: RParameterList [ - RParameter { - name: RDots { - value_token: DOTS@88..91 "..." [] [], - }, - default: missing (optional), - }, - ], - r_paren_token: R_PAREN@91..93 ")" [] [Whitespace(" ")], - }, - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@93..94 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@94..103 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@103..104 "(" [] [], - items: RParameterList [ - RParameter { - name: RIdentifier { - name_token: IDENT@104..105 "a" [] [], - }, - default: missing (optional), - }, - COMMA@105..107 "," [] [Whitespace(" ")], - RParameter { - name: RDots { - value_token: DOTS@107..110 "..." [] [], - }, - default: missing (optional), - }, - COMMA@110..112 "," [] [Whitespace(" ")], - RParameter { - name: RIdentifier { - name_token: IDENT@112..113 "b" [] [], - }, - default: missing (optional), - }, - ], - r_paren_token: R_PAREN@113..115 ")" [] [Whitespace(" ")], - }, - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@115..116 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@116..125 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@125..126 "(" [] [], - items: RParameterList [ - RParameter { - name: RIdentifier { - name_token: IDENT@126..128 "a" [] [Whitespace(" ")], - }, - default: RParameterDefault { - eq_token: EQUAL@128..130 "=" [] [Whitespace(" ")], - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@130..131 "1" [] [], - }, - }, - }, - COMMA@131..133 "," [] [Whitespace(" ")], - RParameter { - name: RDots { - value_token: DOTS@133..136 "..." [] [], - }, - default: missing (optional), - }, - COMMA@136..138 "," [] [Whitespace(" ")], - RParameter { - name: RIdentifier { - name_token: IDENT@138..140 "b" [] [Whitespace(" ")], - }, - default: RParameterDefault { - eq_token: EQUAL@140..142 "=" [] [Whitespace(" ")], - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@142..143 "2" [] [], - }, - }, - }, - ], - r_paren_token: R_PAREN@143..145 ")" [] [Whitespace(" ")], - }, - body: RIdentifier { - name_token: IDENT@145..146 "a" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@146..155 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@155..156 "(" [] [], - items: RParameterList [ - RParameter { - name: RDotDotI { - value_token: DOTDOTI@156..159 "..1" [] [], - }, - default: missing (optional), - }, - COMMA@159..161 "," [] [Whitespace(" ")], - RParameter { - name: RDotDotI { - value_token: DOTDOTI@161..164 "..2" [] [], - }, - default: missing (optional), - }, - ], - r_paren_token: R_PAREN@164..166 ")" [] [Whitespace(" ")], - }, - body: RBinaryExpression { - left: RCall { - function: RIdentifier { - name_token: IDENT@166..169 "get" [] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@169..170 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@170..175 "\"..1\"" [] [], - }, - }, - ], - r_paren_token: R_PAREN@175..177 ")" [] [Whitespace(" ")], - }, - }, - operator: PLUS@177..179 "+" [] [Whitespace(" ")], - right: RCall { - function: RIdentifier { - name_token: IDENT@179..182 "get" [] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@182..183 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@183..188 "\"..2\"" [] [], - }, - }, - ], - r_paren_token: R_PAREN@188..189 ")" [] [], - }, - }, - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@189..198 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@198..199 "(" [] [], - items: RParameterList [ - RParameter { - name: RDotDotI { - value_token: DOTDOTI@199..203 "..1" [] [Whitespace(" ")], - }, - default: RParameterDefault { - eq_token: EQUAL@203..205 "=" [] [Whitespace(" ")], - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@205..206 "1" [] [], - }, - }, - }, - COMMA@206..208 "," [] [Whitespace(" ")], - RParameter { - name: RDotDotI { - value_token: DOTDOTI@208..212 "..2" [] [Whitespace(" ")], - }, - default: RParameterDefault { - eq_token: EQUAL@212..214 "=" [] [Whitespace(" ")], - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@214..215 "2" [] [], - }, - }, - }, - ], - r_paren_token: R_PAREN@215..217 ")" [] [Whitespace(" ")], - }, - body: RBinaryExpression { - left: RCall { - function: RIdentifier { - name_token: IDENT@217..220 "get" [] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@220..221 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@221..226 "\"..1\"" [] [], - }, - }, - ], - r_paren_token: R_PAREN@226..228 ")" [] [Whitespace(" ")], - }, - }, - operator: PLUS@228..230 "+" [] [Whitespace(" ")], - right: RCall { - function: RIdentifier { - name_token: IDENT@230..233 "get" [] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@233..234 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@234..239 "\"..2\"" [] [], - }, - }, - ], - r_paren_token: R_PAREN@239..240 ")" [] [], - }, - }, - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: FUNCTION_KW@240..249 "function" [Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@249..250 "(" [] [], - items: RParameterList [ - RParameter { - name: RIdentifier { - name_token: IDENT@250..255 "x" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - default: RParameterDefault { - eq_token: EQUAL@255..269 "=" [] [Whitespace(" "), Comments("# important!")], - value: RDoubleValue { - value_token: R_DOUBLE_LITERAL@269..273 "4" [Newline("\n"), Whitespace(" ")] [], - }, - }, - }, - ], - r_paren_token: R_PAREN@273..276 ")" [Newline("\n")] [Whitespace(" ")], - }, - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@276..277 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RFunctionDefinition { - name: BACKSLASH@277..280 "\\" [Newline("\n"), Newline("\n")] [], - parameters: RParameters { - l_paren_token: L_PAREN@280..281 "(" [] [], - items: RParameterList [ - RParameter { - name: RIdentifier { - name_token: IDENT@281..282 "x" [] [], - }, - default: missing (optional), - }, - COMMA@282..284 "," [] [Whitespace(" ")], - RParameter { - name: RIdentifier { - name_token: IDENT@284..285 "y" [] [], - }, - default: missing (optional), - }, - ], - r_paren_token: R_PAREN@285..287 ")" [] [Whitespace(" ")], - }, - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@287..288 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@288..289 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..289 - 0: (empty) - 1: R_STATEMENT_LIST@0..288 - 0: R_EXPRESSION_STATEMENT@0..12 - 0: R_FUNCTION_DEFINITION@0..12 - 0: FUNCTION_KW@0..8 "function" [] [] - 1: R_PARAMETERS@8..11 - 0: L_PAREN@8..9 "(" [] [] - 1: R_PARAMETER_LIST@9..9 - 2: R_PAREN@9..11 ")" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@11..12 - 0: R_DOUBLE_LITERAL@11..12 "1" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@12..26 - 0: R_FUNCTION_DEFINITION@12..26 - 0: FUNCTION_KW@12..21 "function" [Newline("\n")] [] - 1: R_PARAMETERS@21..25 - 0: L_PAREN@21..22 "(" [] [] - 1: R_PARAMETER_LIST@22..23 - 0: R_PARAMETER@22..23 - 0: R_IDENTIFIER@22..23 - 0: IDENT@22..23 "a" [] [] - 1: (empty) - 2: R_PAREN@23..25 ")" [] [Whitespace(" ")] - 2: R_IDENTIFIER@25..26 - 0: IDENT@25..26 "a" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@26..47 - 0: R_FUNCTION_DEFINITION@26..47 - 0: FUNCTION_KW@26..35 "function" [Newline("\n")] [] - 1: R_PARAMETERS@35..42 - 0: L_PAREN@35..36 "(" [] [] - 1: R_PARAMETER_LIST@36..40 - 0: R_PARAMETER@36..37 - 0: R_IDENTIFIER@36..37 - 0: IDENT@36..37 "a" [] [] - 1: (empty) - 1: COMMA@37..39 "," [] [Whitespace(" ")] - 2: R_PARAMETER@39..40 - 0: R_IDENTIFIER@39..40 - 0: IDENT@39..40 "b" [] [] - 1: (empty) - 2: R_PAREN@40..42 ")" [] [Whitespace(" ")] - 2: R_BINARY_EXPRESSION@42..47 - 0: R_IDENTIFIER@42..44 - 0: IDENT@42..44 "a" [] [Whitespace(" ")] - 1: PLUS@44..46 "+" [] [Whitespace(" ")] - 2: R_IDENTIFIER@46..47 - 0: IDENT@46..47 "b" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@47..78 - 0: R_FUNCTION_DEFINITION@47..78 - 0: FUNCTION_KW@47..56 "function" [Newline("\n")] [] - 1: R_PARAMETERS@56..77 - 0: L_PAREN@56..57 "(" [] [] - 1: R_PARAMETER_LIST@57..74 - 0: R_PARAMETER@57..74 - 0: R_IDENTIFIER@57..74 - 0: IDENT@57..74 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# important!")] - 1: (empty) - 2: R_PAREN@74..77 ")" [Newline("\n")] [Whitespace(" ")] - 2: R_IDENTIFIER@77..78 - 0: IDENT@77..78 "a" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@78..94 - 0: R_FUNCTION_DEFINITION@78..94 - 0: FUNCTION_KW@78..87 "function" [Newline("\n")] [] - 1: R_PARAMETERS@87..93 - 0: L_PAREN@87..88 "(" [] [] - 1: R_PARAMETER_LIST@88..91 - 0: R_PARAMETER@88..91 - 0: R_DOTS@88..91 - 0: DOTS@88..91 "..." [] [] - 1: (empty) - 2: R_PAREN@91..93 ")" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@93..94 - 0: R_DOUBLE_LITERAL@93..94 "1" [] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@94..116 - 0: R_FUNCTION_DEFINITION@94..116 - 0: FUNCTION_KW@94..103 "function" [Newline("\n")] [] - 1: R_PARAMETERS@103..115 - 0: L_PAREN@103..104 "(" [] [] - 1: R_PARAMETER_LIST@104..113 - 0: R_PARAMETER@104..105 - 0: R_IDENTIFIER@104..105 - 0: IDENT@104..105 "a" [] [] - 1: (empty) - 1: COMMA@105..107 "," [] [Whitespace(" ")] - 2: R_PARAMETER@107..110 - 0: R_DOTS@107..110 - 0: DOTS@107..110 "..." [] [] - 1: (empty) - 3: COMMA@110..112 "," [] [Whitespace(" ")] - 4: R_PARAMETER@112..113 - 0: R_IDENTIFIER@112..113 - 0: IDENT@112..113 "b" [] [] - 1: (empty) - 2: R_PAREN@113..115 ")" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@115..116 - 0: R_DOUBLE_LITERAL@115..116 "1" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@116..146 - 0: R_FUNCTION_DEFINITION@116..146 - 0: FUNCTION_KW@116..125 "function" [Newline("\n")] [] - 1: R_PARAMETERS@125..145 - 0: L_PAREN@125..126 "(" [] [] - 1: R_PARAMETER_LIST@126..143 - 0: R_PARAMETER@126..131 - 0: R_IDENTIFIER@126..128 - 0: IDENT@126..128 "a" [] [Whitespace(" ")] - 1: R_PARAMETER_DEFAULT@128..131 - 0: EQUAL@128..130 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@130..131 - 0: R_DOUBLE_LITERAL@130..131 "1" [] [] - 1: COMMA@131..133 "," [] [Whitespace(" ")] - 2: R_PARAMETER@133..136 - 0: R_DOTS@133..136 - 0: DOTS@133..136 "..." [] [] - 1: (empty) - 3: COMMA@136..138 "," [] [Whitespace(" ")] - 4: R_PARAMETER@138..143 - 0: R_IDENTIFIER@138..140 - 0: IDENT@138..140 "b" [] [Whitespace(" ")] - 1: R_PARAMETER_DEFAULT@140..143 - 0: EQUAL@140..142 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@142..143 - 0: R_DOUBLE_LITERAL@142..143 "2" [] [] - 2: R_PAREN@143..145 ")" [] [Whitespace(" ")] - 2: R_IDENTIFIER@145..146 - 0: IDENT@145..146 "a" [] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@146..189 - 0: R_FUNCTION_DEFINITION@146..189 - 0: FUNCTION_KW@146..155 "function" [Newline("\n")] [] - 1: R_PARAMETERS@155..166 - 0: L_PAREN@155..156 "(" [] [] - 1: R_PARAMETER_LIST@156..164 - 0: R_PARAMETER@156..159 - 0: R_DOT_DOT_I@156..159 - 0: DOTDOTI@156..159 "..1" [] [] - 1: (empty) - 1: COMMA@159..161 "," [] [Whitespace(" ")] - 2: R_PARAMETER@161..164 - 0: R_DOT_DOT_I@161..164 - 0: DOTDOTI@161..164 "..2" [] [] - 1: (empty) - 2: R_PAREN@164..166 ")" [] [Whitespace(" ")] - 2: R_BINARY_EXPRESSION@166..189 - 0: R_CALL@166..177 - 0: R_IDENTIFIER@166..169 - 0: IDENT@166..169 "get" [] [] - 1: R_CALL_ARGUMENTS@169..177 - 0: L_PAREN@169..170 "(" [] [] - 1: R_ARGUMENT_LIST@170..175 - 0: R_ARGUMENT@170..175 - 0: (empty) - 1: R_STRING_VALUE@170..175 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@170..175 "\"..1\"" [] [] - 2: R_PAREN@175..177 ")" [] [Whitespace(" ")] - 1: PLUS@177..179 "+" [] [Whitespace(" ")] - 2: R_CALL@179..189 - 0: R_IDENTIFIER@179..182 - 0: IDENT@179..182 "get" [] [] - 1: R_CALL_ARGUMENTS@182..189 - 0: L_PAREN@182..183 "(" [] [] - 1: R_ARGUMENT_LIST@183..188 - 0: R_ARGUMENT@183..188 - 0: (empty) - 1: R_STRING_VALUE@183..188 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@183..188 "\"..2\"" [] [] - 2: R_PAREN@188..189 ")" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@189..240 - 0: R_FUNCTION_DEFINITION@189..240 - 0: FUNCTION_KW@189..198 "function" [Newline("\n")] [] - 1: R_PARAMETERS@198..217 - 0: L_PAREN@198..199 "(" [] [] - 1: R_PARAMETER_LIST@199..215 - 0: R_PARAMETER@199..206 - 0: R_DOT_DOT_I@199..203 - 0: DOTDOTI@199..203 "..1" [] [Whitespace(" ")] - 1: R_PARAMETER_DEFAULT@203..206 - 0: EQUAL@203..205 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@205..206 - 0: R_DOUBLE_LITERAL@205..206 "1" [] [] - 1: COMMA@206..208 "," [] [Whitespace(" ")] - 2: R_PARAMETER@208..215 - 0: R_DOT_DOT_I@208..212 - 0: DOTDOTI@208..212 "..2" [] [Whitespace(" ")] - 1: R_PARAMETER_DEFAULT@212..215 - 0: EQUAL@212..214 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@214..215 - 0: R_DOUBLE_LITERAL@214..215 "2" [] [] - 2: R_PAREN@215..217 ")" [] [Whitespace(" ")] - 2: R_BINARY_EXPRESSION@217..240 - 0: R_CALL@217..228 - 0: R_IDENTIFIER@217..220 - 0: IDENT@217..220 "get" [] [] - 1: R_CALL_ARGUMENTS@220..228 - 0: L_PAREN@220..221 "(" [] [] - 1: R_ARGUMENT_LIST@221..226 - 0: R_ARGUMENT@221..226 - 0: (empty) - 1: R_STRING_VALUE@221..226 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@221..226 "\"..1\"" [] [] - 2: R_PAREN@226..228 ")" [] [Whitespace(" ")] - 1: PLUS@228..230 "+" [] [Whitespace(" ")] - 2: R_CALL@230..240 - 0: R_IDENTIFIER@230..233 - 0: IDENT@230..233 "get" [] [] - 1: R_CALL_ARGUMENTS@233..240 - 0: L_PAREN@233..234 "(" [] [] - 1: R_ARGUMENT_LIST@234..239 - 0: R_ARGUMENT@234..239 - 0: (empty) - 1: R_STRING_VALUE@234..239 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@234..239 "\"..2\"" [] [] - 2: R_PAREN@239..240 ")" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@240..277 - 0: R_FUNCTION_DEFINITION@240..277 - 0: FUNCTION_KW@240..249 "function" [Newline("\n")] [] - 1: R_PARAMETERS@249..276 - 0: L_PAREN@249..250 "(" [] [] - 1: R_PARAMETER_LIST@250..273 - 0: R_PARAMETER@250..273 - 0: R_IDENTIFIER@250..255 - 0: IDENT@250..255 "x" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: R_PARAMETER_DEFAULT@255..273 - 0: EQUAL@255..269 "=" [] [Whitespace(" "), Comments("# important!")] - 1: R_DOUBLE_VALUE@269..273 - 0: R_DOUBLE_LITERAL@269..273 "4" [Newline("\n"), Whitespace(" ")] [] - 2: R_PAREN@273..276 ")" [Newline("\n")] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@276..277 - 0: R_DOUBLE_LITERAL@276..277 "1" [] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@277..288 - 0: R_FUNCTION_DEFINITION@277..288 - 0: BACKSLASH@277..280 "\\" [Newline("\n"), Newline("\n")] [] - 1: R_PARAMETERS@280..287 - 0: L_PAREN@280..281 "(" [] [] - 1: R_PARAMETER_LIST@281..285 - 0: R_PARAMETER@281..282 - 0: R_IDENTIFIER@281..282 - 0: IDENT@281..282 "x" [] [] - 1: (empty) - 1: COMMA@282..284 "," [] [Whitespace(" ")] - 2: R_PARAMETER@284..285 - 0: R_IDENTIFIER@284..285 - 0: IDENT@284..285 "y" [] [] - 1: (empty) - 2: R_PAREN@285..287 ")" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@287..288 - 0: R_DOUBLE_LITERAL@287..288 "1" [] [] - 1: (empty) - 2: EOF@288..289 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/if_statement.R b/crates/air_r_parser/tests/snapshots/ok/if_statement.R deleted file mode 100644 index 5d54652c..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/if_statement.R +++ /dev/null @@ -1,51 +0,0 @@ -if (a) 1 -if (a) 1 else 2 -if (a) 1 else if (b) 2 else 3 - -if # comment1 - (a) # comment2 - 1 else # comment3 - 2 # comment4 - -# ------------------------------------------------------------------------------ -# `else`-like identifier is not treated as `else` (#499) - -# ASCII letter -{ - if (TRUE) 1 - elseidx <- 1 -} - -# ASCII number -{ - if (TRUE) 1 - else1idx <- 1 -} - -# ASCII `_` -{ - if (TRUE) 1 - else_idx <- 1 -} - -# ASCII `.` -{ - if (TRUE) 1 - else.idx <- 1 -} - -# Non-ASCII, but considered `iswalnum()` in R with UTF-8 locale. -# Not considered `iswalnum()` in our scanner with C locale, but -# our overapproximation with `>= 128` allows this and aligns with R. -{ - if (TRUE) 1 - elseμ <- 1 -} - -# Non-ASCII (U+00B7), rejected by R as syntax error since `iswalnum()` returns false. -# Our overapproximation with `>= 128` allows this, but that's ok since the R -# side behavior is a syntax error. -{ - if (TRUE) 1 - else· <- 1 -} \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/ok/if_statement.R.snap b/crates/air_r_parser/tests/snapshots/ok/if_statement.R.snap deleted file mode 100644 index a820a35a..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/if_statement.R.snap +++ /dev/null @@ -1,599 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -if (a) 1 -if (a) 1 else 2 -if (a) 1 else if (b) 2 else 3 - -if # comment1 - (a) # comment2 - 1 else # comment3 - 2 # comment4 - -# ------------------------------------------------------------------------------ -# `else`-like identifier is not treated as `else` (#499) - -# ASCII letter -{ - if (TRUE) 1 - elseidx <- 1 -} - -# ASCII number -{ - if (TRUE) 1 - else1idx <- 1 -} - -# ASCII `_` -{ - if (TRUE) 1 - else_idx <- 1 -} - -# ASCII `.` -{ - if (TRUE) 1 - else.idx <- 1 -} - -# Non-ASCII, but considered `iswalnum()` in R with UTF-8 locale. -# Not considered `iswalnum()` in our scanner with C locale, but -# our overapproximation with `>= 128` allows this and aligns with R. -{ - if (TRUE) 1 - elseμ <- 1 -} - -# Non-ASCII (U+00B7), rejected by R as syntax error since `iswalnum()` returns false. -# Our overapproximation with `>= 128` allows this, but that's ok since the R -# side behavior is a syntax error. -{ - if (TRUE) 1 - else· <- 1 -} -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], - l_paren_token: L_PAREN@3..4 "(" [] [], - condition: RIdentifier { - name_token: IDENT@4..5 "a" [] [], - }, - r_paren_token: R_PAREN@5..7 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@7..8 "1" [] [], - }, - else_clause: missing (optional), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@8..12 "if" [Newline("\n")] [Whitespace(" ")], - l_paren_token: L_PAREN@12..13 "(" [] [], - condition: RIdentifier { - name_token: IDENT@13..14 "a" [] [], - }, - r_paren_token: R_PAREN@14..16 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@16..18 "1" [] [Whitespace(" ")], - }, - else_clause: RElseClause { - else_token: ELSE_KW@18..23 "else" [] [Whitespace(" ")], - alternative: RDoubleValue { - value_token: R_DOUBLE_LITERAL@23..24 "2" [] [], - }, - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@24..28 "if" [Newline("\n")] [Whitespace(" ")], - l_paren_token: L_PAREN@28..29 "(" [] [], - condition: RIdentifier { - name_token: IDENT@29..30 "a" [] [], - }, - r_paren_token: R_PAREN@30..32 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@32..34 "1" [] [Whitespace(" ")], - }, - else_clause: RElseClause { - else_token: ELSE_KW@34..39 "else" [] [Whitespace(" ")], - alternative: RIfStatement { - if_token: IF_KW@39..42 "if" [] [Whitespace(" ")], - l_paren_token: L_PAREN@42..43 "(" [] [], - condition: RIdentifier { - name_token: IDENT@43..44 "b" [] [], - }, - r_paren_token: R_PAREN@44..46 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@46..48 "2" [] [Whitespace(" ")], - }, - else_clause: RElseClause { - else_token: ELSE_KW@48..53 "else" [] [Whitespace(" ")], - alternative: RDoubleValue { - value_token: R_DOUBLE_LITERAL@53..54 "3" [] [], - }, - }, - }, - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@54..69 "if" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# comment1")], - l_paren_token: L_PAREN@69..73 "(" [Newline("\n"), Whitespace(" ")] [], - condition: RIdentifier { - name_token: IDENT@73..74 "a" [] [], - }, - r_paren_token: R_PAREN@74..86 ")" [] [Whitespace(" "), Comments("# comment2")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@86..91 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - else_clause: RElseClause { - else_token: ELSE_KW@91..106 "else" [] [Whitespace(" "), Comments("# comment3")], - alternative: RDoubleValue { - value_token: R_DOUBLE_LITERAL@106..121 "2" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment4")], - }, - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@121..278 "{" [Newline("\n"), Newline("\n"), Comments("# ------------------- ..."), Newline("\n"), Comments("# `else`-like identif ..."), Newline("\n"), Newline("\n"), Comments("# ASCII letter"), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@278..284 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - l_paren_token: L_PAREN@284..285 "(" [] [], - condition: RTrueExpression { - true_token: TRUE_KW@285..289 "TRUE" [] [], - }, - r_paren_token: R_PAREN@289..291 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@291..292 "1" [] [], - }, - else_clause: missing (optional), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RIdentifier { - name_token: IDENT@292..303 "elseidx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: ASSIGN@303..306 "<-" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@306..307 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@307..309 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@309..327 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII number"), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@327..333 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - l_paren_token: L_PAREN@333..334 "(" [] [], - condition: RTrueExpression { - true_token: TRUE_KW@334..338 "TRUE" [] [], - }, - r_paren_token: R_PAREN@338..340 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@340..341 "1" [] [], - }, - else_clause: missing (optional), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RIdentifier { - name_token: IDENT@341..353 "else1idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: ASSIGN@353..356 "<-" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@356..357 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@357..359 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@359..374 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII `_`"), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@374..380 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - l_paren_token: L_PAREN@380..381 "(" [] [], - condition: RTrueExpression { - true_token: TRUE_KW@381..385 "TRUE" [] [], - }, - r_paren_token: R_PAREN@385..387 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@387..388 "1" [] [], - }, - else_clause: missing (optional), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RIdentifier { - name_token: IDENT@388..400 "else_idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: ASSIGN@400..403 "<-" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@403..404 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@404..406 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@406..421 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII `.`"), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@421..427 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - l_paren_token: L_PAREN@427..428 "(" [] [], - condition: RTrueExpression { - true_token: TRUE_KW@428..432 "TRUE" [] [], - }, - r_paren_token: R_PAREN@432..434 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@434..435 "1" [] [], - }, - else_clause: missing (optional), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RIdentifier { - name_token: IDENT@435..447 "else.idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: ASSIGN@447..450 "<-" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@450..451 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@451..453 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@453..654 "{" [Newline("\n"), Newline("\n"), Comments("# Non-ASCII, but cons ..."), Newline("\n"), Comments("# Not considered `isw ..."), Newline("\n"), Comments("# our overapproximati ..."), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@654..660 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - l_paren_token: L_PAREN@660..661 "(" [] [], - condition: RTrueExpression { - true_token: TRUE_KW@661..665 "TRUE" [] [], - }, - r_paren_token: R_PAREN@665..667 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@667..668 "1" [] [], - }, - else_clause: missing (optional), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RIdentifier { - name_token: IDENT@668..678 "elseμ" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: ASSIGN@678..681 "<-" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@681..682 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@682..684 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@684..885 "{" [Newline("\n"), Newline("\n"), Comments("# Non-ASCII (U+00B7), ..."), Newline("\n"), Comments("# Our overapproximati ..."), Newline("\n"), Comments("# side behavior is a ..."), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RIfStatement { - if_token: IF_KW@885..891 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - l_paren_token: L_PAREN@891..892 "(" [] [], - condition: RTrueExpression { - true_token: TRUE_KW@892..896 "TRUE" [] [], - }, - r_paren_token: R_PAREN@896..898 ")" [] [Whitespace(" ")], - consequence: RDoubleValue { - value_token: R_DOUBLE_LITERAL@898..899 "1" [] [], - }, - else_clause: missing (optional), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RIdentifier { - name_token: IDENT@899..909 "else·" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: ASSIGN@909..912 "<-" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@912..913 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@913..915 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@915..915 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..915 - 0: (empty) - 1: R_STATEMENT_LIST@0..915 - 0: R_EXPRESSION_STATEMENT@0..8 - 0: R_IF_STATEMENT@0..8 - 0: IF_KW@0..3 "if" [] [Whitespace(" ")] - 1: L_PAREN@3..4 "(" [] [] - 2: R_IDENTIFIER@4..5 - 0: IDENT@4..5 "a" [] [] - 3: R_PAREN@5..7 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@7..8 - 0: R_DOUBLE_LITERAL@7..8 "1" [] [] - 5: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@8..24 - 0: R_IF_STATEMENT@8..24 - 0: IF_KW@8..12 "if" [Newline("\n")] [Whitespace(" ")] - 1: L_PAREN@12..13 "(" [] [] - 2: R_IDENTIFIER@13..14 - 0: IDENT@13..14 "a" [] [] - 3: R_PAREN@14..16 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@16..18 - 0: R_DOUBLE_LITERAL@16..18 "1" [] [Whitespace(" ")] - 5: R_ELSE_CLAUSE@18..24 - 0: ELSE_KW@18..23 "else" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@23..24 - 0: R_DOUBLE_LITERAL@23..24 "2" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@24..54 - 0: R_IF_STATEMENT@24..54 - 0: IF_KW@24..28 "if" [Newline("\n")] [Whitespace(" ")] - 1: L_PAREN@28..29 "(" [] [] - 2: R_IDENTIFIER@29..30 - 0: IDENT@29..30 "a" [] [] - 3: R_PAREN@30..32 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@32..34 - 0: R_DOUBLE_LITERAL@32..34 "1" [] [Whitespace(" ")] - 5: R_ELSE_CLAUSE@34..54 - 0: ELSE_KW@34..39 "else" [] [Whitespace(" ")] - 1: R_IF_STATEMENT@39..54 - 0: IF_KW@39..42 "if" [] [Whitespace(" ")] - 1: L_PAREN@42..43 "(" [] [] - 2: R_IDENTIFIER@43..44 - 0: IDENT@43..44 "b" [] [] - 3: R_PAREN@44..46 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@46..48 - 0: R_DOUBLE_LITERAL@46..48 "2" [] [Whitespace(" ")] - 5: R_ELSE_CLAUSE@48..54 - 0: ELSE_KW@48..53 "else" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@53..54 - 0: R_DOUBLE_LITERAL@53..54 "3" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@54..121 - 0: R_IF_STATEMENT@54..121 - 0: IF_KW@54..69 "if" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# comment1")] - 1: L_PAREN@69..73 "(" [Newline("\n"), Whitespace(" ")] [] - 2: R_IDENTIFIER@73..74 - 0: IDENT@73..74 "a" [] [] - 3: R_PAREN@74..86 ")" [] [Whitespace(" "), Comments("# comment2")] - 4: R_DOUBLE_VALUE@86..91 - 0: R_DOUBLE_LITERAL@86..91 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 5: R_ELSE_CLAUSE@91..121 - 0: ELSE_KW@91..106 "else" [] [Whitespace(" "), Comments("# comment3")] - 1: R_DOUBLE_VALUE@106..121 - 0: R_DOUBLE_LITERAL@106..121 "2" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment4")] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@121..309 - 0: R_BRACED_STATEMENTS@121..309 - 0: L_CURLY@121..278 "{" [Newline("\n"), Newline("\n"), Comments("# ------------------- ..."), Newline("\n"), Comments("# `else`-like identif ..."), Newline("\n"), Newline("\n"), Comments("# ASCII letter"), Newline("\n")] [] - 1: R_STATEMENT_LIST@278..307 - 0: R_EXPRESSION_STATEMENT@278..292 - 0: R_IF_STATEMENT@278..292 - 0: IF_KW@278..284 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: L_PAREN@284..285 "(" [] [] - 2: R_TRUE_EXPRESSION@285..289 - 0: TRUE_KW@285..289 "TRUE" [] [] - 3: R_PAREN@289..291 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@291..292 - 0: R_DOUBLE_LITERAL@291..292 "1" [] [] - 5: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@292..307 - 0: R_BINARY_EXPRESSION@292..307 - 0: R_IDENTIFIER@292..303 - 0: IDENT@292..303 "elseidx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: ASSIGN@303..306 "<-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@306..307 - 0: R_DOUBLE_LITERAL@306..307 "1" [] [] - 1: (empty) - 2: R_CURLY@307..309 "}" [Newline("\n")] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@309..359 - 0: R_BRACED_STATEMENTS@309..359 - 0: L_CURLY@309..327 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII number"), Newline("\n")] [] - 1: R_STATEMENT_LIST@327..357 - 0: R_EXPRESSION_STATEMENT@327..341 - 0: R_IF_STATEMENT@327..341 - 0: IF_KW@327..333 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: L_PAREN@333..334 "(" [] [] - 2: R_TRUE_EXPRESSION@334..338 - 0: TRUE_KW@334..338 "TRUE" [] [] - 3: R_PAREN@338..340 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@340..341 - 0: R_DOUBLE_LITERAL@340..341 "1" [] [] - 5: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@341..357 - 0: R_BINARY_EXPRESSION@341..357 - 0: R_IDENTIFIER@341..353 - 0: IDENT@341..353 "else1idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: ASSIGN@353..356 "<-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@356..357 - 0: R_DOUBLE_LITERAL@356..357 "1" [] [] - 1: (empty) - 2: R_CURLY@357..359 "}" [Newline("\n")] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@359..406 - 0: R_BRACED_STATEMENTS@359..406 - 0: L_CURLY@359..374 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII `_`"), Newline("\n")] [] - 1: R_STATEMENT_LIST@374..404 - 0: R_EXPRESSION_STATEMENT@374..388 - 0: R_IF_STATEMENT@374..388 - 0: IF_KW@374..380 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: L_PAREN@380..381 "(" [] [] - 2: R_TRUE_EXPRESSION@381..385 - 0: TRUE_KW@381..385 "TRUE" [] [] - 3: R_PAREN@385..387 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@387..388 - 0: R_DOUBLE_LITERAL@387..388 "1" [] [] - 5: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@388..404 - 0: R_BINARY_EXPRESSION@388..404 - 0: R_IDENTIFIER@388..400 - 0: IDENT@388..400 "else_idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: ASSIGN@400..403 "<-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@403..404 - 0: R_DOUBLE_LITERAL@403..404 "1" [] [] - 1: (empty) - 2: R_CURLY@404..406 "}" [Newline("\n")] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@406..453 - 0: R_BRACED_STATEMENTS@406..453 - 0: L_CURLY@406..421 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII `.`"), Newline("\n")] [] - 1: R_STATEMENT_LIST@421..451 - 0: R_EXPRESSION_STATEMENT@421..435 - 0: R_IF_STATEMENT@421..435 - 0: IF_KW@421..427 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: L_PAREN@427..428 "(" [] [] - 2: R_TRUE_EXPRESSION@428..432 - 0: TRUE_KW@428..432 "TRUE" [] [] - 3: R_PAREN@432..434 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@434..435 - 0: R_DOUBLE_LITERAL@434..435 "1" [] [] - 5: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@435..451 - 0: R_BINARY_EXPRESSION@435..451 - 0: R_IDENTIFIER@435..447 - 0: IDENT@435..447 "else.idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: ASSIGN@447..450 "<-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@450..451 - 0: R_DOUBLE_LITERAL@450..451 "1" [] [] - 1: (empty) - 2: R_CURLY@451..453 "}" [Newline("\n")] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@453..684 - 0: R_BRACED_STATEMENTS@453..684 - 0: L_CURLY@453..654 "{" [Newline("\n"), Newline("\n"), Comments("# Non-ASCII, but cons ..."), Newline("\n"), Comments("# Not considered `isw ..."), Newline("\n"), Comments("# our overapproximati ..."), Newline("\n")] [] - 1: R_STATEMENT_LIST@654..682 - 0: R_EXPRESSION_STATEMENT@654..668 - 0: R_IF_STATEMENT@654..668 - 0: IF_KW@654..660 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: L_PAREN@660..661 "(" [] [] - 2: R_TRUE_EXPRESSION@661..665 - 0: TRUE_KW@661..665 "TRUE" [] [] - 3: R_PAREN@665..667 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@667..668 - 0: R_DOUBLE_LITERAL@667..668 "1" [] [] - 5: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@668..682 - 0: R_BINARY_EXPRESSION@668..682 - 0: R_IDENTIFIER@668..678 - 0: IDENT@668..678 "elseμ" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: ASSIGN@678..681 "<-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@681..682 - 0: R_DOUBLE_LITERAL@681..682 "1" [] [] - 1: (empty) - 2: R_CURLY@682..684 "}" [Newline("\n")] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@684..915 - 0: R_BRACED_STATEMENTS@684..915 - 0: L_CURLY@684..885 "{" [Newline("\n"), Newline("\n"), Comments("# Non-ASCII (U+00B7), ..."), Newline("\n"), Comments("# Our overapproximati ..."), Newline("\n"), Comments("# side behavior is a ..."), Newline("\n")] [] - 1: R_STATEMENT_LIST@885..913 - 0: R_EXPRESSION_STATEMENT@885..899 - 0: R_IF_STATEMENT@885..899 - 0: IF_KW@885..891 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: L_PAREN@891..892 "(" [] [] - 2: R_TRUE_EXPRESSION@892..896 - 0: TRUE_KW@892..896 "TRUE" [] [] - 3: R_PAREN@896..898 ")" [] [Whitespace(" ")] - 4: R_DOUBLE_VALUE@898..899 - 0: R_DOUBLE_LITERAL@898..899 "1" [] [] - 5: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@899..913 - 0: R_BINARY_EXPRESSION@899..913 - 0: R_IDENTIFIER@899..909 - 0: IDENT@899..909 "else·" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: ASSIGN@909..912 "<-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@912..913 - 0: R_DOUBLE_LITERAL@912..913 "1" [] [] - 1: (empty) - 2: R_CURLY@913..915 "}" [Newline("\n")] [] - 1: (empty) - 2: EOF@915..915 "" [] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/namespace_expression.R b/crates/air_r_parser/tests/snapshots/ok/namespace_expression.R deleted file mode 100644 index bbe9e5b3..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/namespace_expression.R +++ /dev/null @@ -1,32 +0,0 @@ -a::b -a:::b - -a::`b` -a:::`b` - -a::"b" -a:::"b" - -a::b() -a:::b() - -a::b$c() -a:::b@c() - -a::... -a:::... - -...::a -...:::a - -...::... -...:::... - -a::..1 -a:::..1 - -..1::a -..1:::a - -..1::..1 -..1:::..1 diff --git a/crates/air_r_parser/tests/snapshots/ok/namespace_expression.R.snap b/crates/air_r_parser/tests/snapshots/ok/namespace_expression.R.snap deleted file mode 100644 index ac79f309..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/namespace_expression.R.snap +++ /dev/null @@ -1,571 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -a::b -a:::b - -a::`b` -a:::`b` - -a::"b" -a:::"b" - -a::b() -a:::b() - -a::b$c() -a:::b@c() - -a::... -a:::... - -...::a -...:::a - -...::... -...:::... - -a::..1 -a:::..1 - -..1::a -..1:::a - -..1::..1 -..1:::..1 - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@0..1 "a" [] [], - }, - operator: COLON2@1..3 "::" [] [], - right: RIdentifier { - name_token: IDENT@3..4 "b" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@4..6 "a" [Newline("\n")] [], - }, - operator: COLON3@6..9 ":::" [] [], - right: RIdentifier { - name_token: IDENT@9..10 "b" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@10..13 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@13..15 "::" [] [], - right: RIdentifier { - name_token: IDENT@15..18 "`b`" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@18..20 "a" [Newline("\n")] [], - }, - operator: COLON3@20..23 ":::" [] [], - right: RIdentifier { - name_token: IDENT@23..26 "`b`" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@26..29 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@29..31 "::" [] [], - right: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@31..34 "\"b\"" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@34..36 "a" [Newline("\n")] [], - }, - operator: COLON3@36..39 ":::" [] [], - right: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@39..42 "\"b\"" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@42..45 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@45..47 "::" [] [], - right: RIdentifier { - name_token: IDENT@47..48 "b" [] [], - }, - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@48..49 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@49..50 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@50..52 "a" [Newline("\n")] [], - }, - operator: COLON3@52..55 ":::" [] [], - right: RIdentifier { - name_token: IDENT@55..56 "b" [] [], - }, - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@56..57 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@57..58 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RExtractExpression { - left: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@58..61 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@61..63 "::" [] [], - right: RIdentifier { - name_token: IDENT@63..64 "b" [] [], - }, - }, - operator: DOLLAR@64..65 "$" [] [], - right: RIdentifier { - name_token: IDENT@65..66 "c" [] [], - }, - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@66..67 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@67..68 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RExtractExpression { - left: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@68..70 "a" [Newline("\n")] [], - }, - operator: COLON3@70..73 ":::" [] [], - right: RIdentifier { - name_token: IDENT@73..74 "b" [] [], - }, - }, - operator: AT@74..75 "@" [] [], - right: RIdentifier { - name_token: IDENT@75..76 "c" [] [], - }, - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@76..77 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@77..78 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@78..81 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@81..83 "::" [] [], - right: RDots { - value_token: DOTS@83..86 "..." [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@86..88 "a" [Newline("\n")] [], - }, - operator: COLON3@88..91 ":::" [] [], - right: RDots { - value_token: DOTS@91..94 "..." [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RDots { - value_token: DOTS@94..99 "..." [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@99..101 "::" [] [], - right: RIdentifier { - name_token: IDENT@101..102 "a" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RDots { - value_token: DOTS@102..106 "..." [Newline("\n")] [], - }, - operator: COLON3@106..109 ":::" [] [], - right: RIdentifier { - name_token: IDENT@109..110 "a" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RDots { - value_token: DOTS@110..115 "..." [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@115..117 "::" [] [], - right: RDots { - value_token: DOTS@117..120 "..." [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RDots { - value_token: DOTS@120..124 "..." [Newline("\n")] [], - }, - operator: COLON3@124..127 ":::" [] [], - right: RDots { - value_token: DOTS@127..130 "..." [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@130..133 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@133..135 "::" [] [], - right: RDotDotI { - value_token: DOTDOTI@135..138 "..1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@138..140 "a" [Newline("\n")] [], - }, - operator: COLON3@140..143 ":::" [] [], - right: RDotDotI { - value_token: DOTDOTI@143..146 "..1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RDotDotI { - value_token: DOTDOTI@146..151 "..1" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@151..153 "::" [] [], - right: RIdentifier { - name_token: IDENT@153..154 "a" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RDotDotI { - value_token: DOTDOTI@154..158 "..1" [Newline("\n")] [], - }, - operator: COLON3@158..161 ":::" [] [], - right: RIdentifier { - name_token: IDENT@161..162 "a" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RDotDotI { - value_token: DOTDOTI@162..167 "..1" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@167..169 "::" [] [], - right: RDotDotI { - value_token: DOTDOTI@169..172 "..1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RDotDotI { - value_token: DOTDOTI@172..176 "..1" [Newline("\n")] [], - }, - operator: COLON3@176..179 ":::" [] [], - right: RDotDotI { - value_token: DOTDOTI@179..182 "..1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@182..183 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..183 - 0: (empty) - 1: R_STATEMENT_LIST@0..182 - 0: R_EXPRESSION_STATEMENT@0..4 - 0: R_NAMESPACE_EXPRESSION@0..4 - 0: R_IDENTIFIER@0..1 - 0: IDENT@0..1 "a" [] [] - 1: COLON2@1..3 "::" [] [] - 2: R_IDENTIFIER@3..4 - 0: IDENT@3..4 "b" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@4..10 - 0: R_NAMESPACE_EXPRESSION@4..10 - 0: R_IDENTIFIER@4..6 - 0: IDENT@4..6 "a" [Newline("\n")] [] - 1: COLON3@6..9 ":::" [] [] - 2: R_IDENTIFIER@9..10 - 0: IDENT@9..10 "b" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@10..18 - 0: R_NAMESPACE_EXPRESSION@10..18 - 0: R_IDENTIFIER@10..13 - 0: IDENT@10..13 "a" [Newline("\n"), Newline("\n")] [] - 1: COLON2@13..15 "::" [] [] - 2: R_IDENTIFIER@15..18 - 0: IDENT@15..18 "`b`" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@18..26 - 0: R_NAMESPACE_EXPRESSION@18..26 - 0: R_IDENTIFIER@18..20 - 0: IDENT@18..20 "a" [Newline("\n")] [] - 1: COLON3@20..23 ":::" [] [] - 2: R_IDENTIFIER@23..26 - 0: IDENT@23..26 "`b`" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@26..34 - 0: R_NAMESPACE_EXPRESSION@26..34 - 0: R_IDENTIFIER@26..29 - 0: IDENT@26..29 "a" [Newline("\n"), Newline("\n")] [] - 1: COLON2@29..31 "::" [] [] - 2: R_STRING_VALUE@31..34 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@31..34 "\"b\"" [] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@34..42 - 0: R_NAMESPACE_EXPRESSION@34..42 - 0: R_IDENTIFIER@34..36 - 0: IDENT@34..36 "a" [Newline("\n")] [] - 1: COLON3@36..39 ":::" [] [] - 2: R_STRING_VALUE@39..42 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@39..42 "\"b\"" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@42..50 - 0: R_CALL@42..50 - 0: R_NAMESPACE_EXPRESSION@42..48 - 0: R_IDENTIFIER@42..45 - 0: IDENT@42..45 "a" [Newline("\n"), Newline("\n")] [] - 1: COLON2@45..47 "::" [] [] - 2: R_IDENTIFIER@47..48 - 0: IDENT@47..48 "b" [] [] - 1: R_CALL_ARGUMENTS@48..50 - 0: L_PAREN@48..49 "(" [] [] - 1: R_ARGUMENT_LIST@49..49 - 2: R_PAREN@49..50 ")" [] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@50..58 - 0: R_CALL@50..58 - 0: R_NAMESPACE_EXPRESSION@50..56 - 0: R_IDENTIFIER@50..52 - 0: IDENT@50..52 "a" [Newline("\n")] [] - 1: COLON3@52..55 ":::" [] [] - 2: R_IDENTIFIER@55..56 - 0: IDENT@55..56 "b" [] [] - 1: R_CALL_ARGUMENTS@56..58 - 0: L_PAREN@56..57 "(" [] [] - 1: R_ARGUMENT_LIST@57..57 - 2: R_PAREN@57..58 ")" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@58..68 - 0: R_CALL@58..68 - 0: R_EXTRACT_EXPRESSION@58..66 - 0: R_NAMESPACE_EXPRESSION@58..64 - 0: R_IDENTIFIER@58..61 - 0: IDENT@58..61 "a" [Newline("\n"), Newline("\n")] [] - 1: COLON2@61..63 "::" [] [] - 2: R_IDENTIFIER@63..64 - 0: IDENT@63..64 "b" [] [] - 1: DOLLAR@64..65 "$" [] [] - 2: R_IDENTIFIER@65..66 - 0: IDENT@65..66 "c" [] [] - 1: R_CALL_ARGUMENTS@66..68 - 0: L_PAREN@66..67 "(" [] [] - 1: R_ARGUMENT_LIST@67..67 - 2: R_PAREN@67..68 ")" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@68..78 - 0: R_CALL@68..78 - 0: R_EXTRACT_EXPRESSION@68..76 - 0: R_NAMESPACE_EXPRESSION@68..74 - 0: R_IDENTIFIER@68..70 - 0: IDENT@68..70 "a" [Newline("\n")] [] - 1: COLON3@70..73 ":::" [] [] - 2: R_IDENTIFIER@73..74 - 0: IDENT@73..74 "b" [] [] - 1: AT@74..75 "@" [] [] - 2: R_IDENTIFIER@75..76 - 0: IDENT@75..76 "c" [] [] - 1: R_CALL_ARGUMENTS@76..78 - 0: L_PAREN@76..77 "(" [] [] - 1: R_ARGUMENT_LIST@77..77 - 2: R_PAREN@77..78 ")" [] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@78..86 - 0: R_NAMESPACE_EXPRESSION@78..86 - 0: R_IDENTIFIER@78..81 - 0: IDENT@78..81 "a" [Newline("\n"), Newline("\n")] [] - 1: COLON2@81..83 "::" [] [] - 2: R_DOTS@83..86 - 0: DOTS@83..86 "..." [] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@86..94 - 0: R_NAMESPACE_EXPRESSION@86..94 - 0: R_IDENTIFIER@86..88 - 0: IDENT@86..88 "a" [Newline("\n")] [] - 1: COLON3@88..91 ":::" [] [] - 2: R_DOTS@91..94 - 0: DOTS@91..94 "..." [] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@94..102 - 0: R_NAMESPACE_EXPRESSION@94..102 - 0: R_DOTS@94..99 - 0: DOTS@94..99 "..." [Newline("\n"), Newline("\n")] [] - 1: COLON2@99..101 "::" [] [] - 2: R_IDENTIFIER@101..102 - 0: IDENT@101..102 "a" [] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@102..110 - 0: R_NAMESPACE_EXPRESSION@102..110 - 0: R_DOTS@102..106 - 0: DOTS@102..106 "..." [Newline("\n")] [] - 1: COLON3@106..109 ":::" [] [] - 2: R_IDENTIFIER@109..110 - 0: IDENT@109..110 "a" [] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@110..120 - 0: R_NAMESPACE_EXPRESSION@110..120 - 0: R_DOTS@110..115 - 0: DOTS@110..115 "..." [Newline("\n"), Newline("\n")] [] - 1: COLON2@115..117 "::" [] [] - 2: R_DOTS@117..120 - 0: DOTS@117..120 "..." [] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@120..130 - 0: R_NAMESPACE_EXPRESSION@120..130 - 0: R_DOTS@120..124 - 0: DOTS@120..124 "..." [Newline("\n")] [] - 1: COLON3@124..127 ":::" [] [] - 2: R_DOTS@127..130 - 0: DOTS@127..130 "..." [] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@130..138 - 0: R_NAMESPACE_EXPRESSION@130..138 - 0: R_IDENTIFIER@130..133 - 0: IDENT@130..133 "a" [Newline("\n"), Newline("\n")] [] - 1: COLON2@133..135 "::" [] [] - 2: R_DOT_DOT_I@135..138 - 0: DOTDOTI@135..138 "..1" [] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@138..146 - 0: R_NAMESPACE_EXPRESSION@138..146 - 0: R_IDENTIFIER@138..140 - 0: IDENT@138..140 "a" [Newline("\n")] [] - 1: COLON3@140..143 ":::" [] [] - 2: R_DOT_DOT_I@143..146 - 0: DOTDOTI@143..146 "..1" [] [] - 1: (empty) - 18: R_EXPRESSION_STATEMENT@146..154 - 0: R_NAMESPACE_EXPRESSION@146..154 - 0: R_DOT_DOT_I@146..151 - 0: DOTDOTI@146..151 "..1" [Newline("\n"), Newline("\n")] [] - 1: COLON2@151..153 "::" [] [] - 2: R_IDENTIFIER@153..154 - 0: IDENT@153..154 "a" [] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@154..162 - 0: R_NAMESPACE_EXPRESSION@154..162 - 0: R_DOT_DOT_I@154..158 - 0: DOTDOTI@154..158 "..1" [Newline("\n")] [] - 1: COLON3@158..161 ":::" [] [] - 2: R_IDENTIFIER@161..162 - 0: IDENT@161..162 "a" [] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@162..172 - 0: R_NAMESPACE_EXPRESSION@162..172 - 0: R_DOT_DOT_I@162..167 - 0: DOTDOTI@162..167 "..1" [Newline("\n"), Newline("\n")] [] - 1: COLON2@167..169 "::" [] [] - 2: R_DOT_DOT_I@169..172 - 0: DOTDOTI@169..172 "..1" [] [] - 1: (empty) - 21: R_EXPRESSION_STATEMENT@172..182 - 0: R_NAMESPACE_EXPRESSION@172..182 - 0: R_DOT_DOT_I@172..176 - 0: DOTDOTI@172..176 "..1" [Newline("\n")] [] - 1: COLON3@176..179 ":::" [] [] - 2: R_DOT_DOT_I@179..182 - 0: DOTDOTI@179..182 "..1" [] [] - 1: (empty) - 2: EOF@182..183 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/parenthesized_expression.R b/crates/air_r_parser/tests/snapshots/ok/parenthesized_expression.R deleted file mode 100644 index aee6d4c6..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/parenthesized_expression.R +++ /dev/null @@ -1,13 +0,0 @@ -(1) - -({ - 1 + 1 -}) - -((1)) - -( # comment1 - # comment2 - 1 # comment3 - # comment4 -) # comment5 diff --git a/crates/air_r_parser/tests/snapshots/ok/parenthesized_expression.R.snap b/crates/air_r_parser/tests/snapshots/ok/parenthesized_expression.R.snap deleted file mode 100644 index 921cf8c2..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/parenthesized_expression.R.snap +++ /dev/null @@ -1,144 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -(1) - -({ - 1 + 1 -}) - -((1)) - -( # comment1 - # comment2 - 1 # comment3 - # comment4 -) # comment5 - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RParenthesizedExpression { - l_paren_token: L_PAREN@0..1 "(" [] [], - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@1..2 "1" [] [], - }, - r_paren_token: R_PAREN@2..3 ")" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RParenthesizedExpression { - l_paren_token: L_PAREN@3..6 "(" [Newline("\n"), Newline("\n")] [], - body: RBracedStatements { - l_curly_token: L_CURLY@6..7 "{" [] [], - statements: RStatementList [ - RExpressionStatement { - expression: RBinaryExpression { - left: RDoubleValue { - value_token: R_DOUBLE_LITERAL@7..12 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: PLUS@12..14 "+" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@14..15 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@15..17 "}" [Newline("\n")] [], - }, - r_paren_token: R_PAREN@17..18 ")" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RParenthesizedExpression { - l_paren_token: L_PAREN@18..21 "(" [Newline("\n"), Newline("\n")] [], - body: RParenthesizedExpression { - l_paren_token: L_PAREN@21..22 "(" [] [], - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@22..23 "1" [] [], - }, - r_paren_token: R_PAREN@23..24 ")" [] [], - }, - r_paren_token: R_PAREN@24..25 ")" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RParenthesizedExpression { - l_paren_token: L_PAREN@25..39 "(" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# comment1")], - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@39..67 "1" [Newline("\n"), Whitespace(" "), Comments("# comment2"), Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")], - }, - r_paren_token: R_PAREN@67..93 ")" [Newline("\n"), Whitespace(" "), Comments("# comment4"), Newline("\n")] [Whitespace(" "), Comments("# comment5")], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@93..94 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..94 - 0: (empty) - 1: R_STATEMENT_LIST@0..93 - 0: R_EXPRESSION_STATEMENT@0..3 - 0: R_PARENTHESIZED_EXPRESSION@0..3 - 0: L_PAREN@0..1 "(" [] [] - 1: R_DOUBLE_VALUE@1..2 - 0: R_DOUBLE_LITERAL@1..2 "1" [] [] - 2: R_PAREN@2..3 ")" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@3..18 - 0: R_PARENTHESIZED_EXPRESSION@3..18 - 0: L_PAREN@3..6 "(" [Newline("\n"), Newline("\n")] [] - 1: R_BRACED_STATEMENTS@6..17 - 0: L_CURLY@6..7 "{" [] [] - 1: R_STATEMENT_LIST@7..15 - 0: R_EXPRESSION_STATEMENT@7..15 - 0: R_BINARY_EXPRESSION@7..15 - 0: R_DOUBLE_VALUE@7..12 - 0: R_DOUBLE_LITERAL@7..12 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: PLUS@12..14 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@14..15 - 0: R_DOUBLE_LITERAL@14..15 "1" [] [] - 1: (empty) - 2: R_CURLY@15..17 "}" [Newline("\n")] [] - 2: R_PAREN@17..18 ")" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@18..25 - 0: R_PARENTHESIZED_EXPRESSION@18..25 - 0: L_PAREN@18..21 "(" [Newline("\n"), Newline("\n")] [] - 1: R_PARENTHESIZED_EXPRESSION@21..24 - 0: L_PAREN@21..22 "(" [] [] - 1: R_DOUBLE_VALUE@22..23 - 0: R_DOUBLE_LITERAL@22..23 "1" [] [] - 2: R_PAREN@23..24 ")" [] [] - 2: R_PAREN@24..25 ")" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@25..93 - 0: R_PARENTHESIZED_EXPRESSION@25..93 - 0: L_PAREN@25..39 "(" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# comment1")] - 1: R_DOUBLE_VALUE@39..67 - 0: R_DOUBLE_LITERAL@39..67 "1" [Newline("\n"), Whitespace(" "), Comments("# comment2"), Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")] - 2: R_PAREN@67..93 ")" [Newline("\n"), Whitespace(" "), Comments("# comment4"), Newline("\n")] [Whitespace(" "), Comments("# comment5")] - 1: (empty) - 2: EOF@93..94 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/repeat_statement.R b/crates/air_r_parser/tests/snapshots/ok/repeat_statement.R deleted file mode 100644 index 0d184b0e..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/repeat_statement.R +++ /dev/null @@ -1,8 +0,0 @@ -repeat 1 - -repeat { -} - -repeat { - 1 + 1 -} diff --git a/crates/air_r_parser/tests/snapshots/ok/repeat_statement.R.snap b/crates/air_r_parser/tests/snapshots/ok/repeat_statement.R.snap deleted file mode 100644 index cd17c760..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/repeat_statement.R.snap +++ /dev/null @@ -1,113 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -repeat 1 - -repeat { -} - -repeat { - 1 + 1 -} - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RRepeatStatement { - repeat_token: REPEAT_KW@0..7 "repeat" [] [Whitespace(" ")], - body: RDoubleValue { - value_token: R_DOUBLE_LITERAL@7..8 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RRepeatStatement { - repeat_token: REPEAT_KW@8..17 "repeat" [Newline("\n"), Newline("\n")] [Whitespace(" ")], - body: RBracedStatements { - l_curly_token: L_CURLY@17..18 "{" [] [], - statements: RStatementList [], - r_curly_token: R_CURLY@18..20 "}" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RRepeatStatement { - repeat_token: REPEAT_KW@20..29 "repeat" [Newline("\n"), Newline("\n")] [Whitespace(" ")], - body: RBracedStatements { - l_curly_token: L_CURLY@29..30 "{" [] [], - statements: RStatementList [ - RExpressionStatement { - expression: RBinaryExpression { - left: RDoubleValue { - value_token: R_DOUBLE_LITERAL@30..35 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], - }, - operator: PLUS@35..37 "+" [] [Whitespace(" ")], - right: RDoubleValue { - value_token: R_DOUBLE_LITERAL@37..38 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@38..40 "}" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@40..41 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..41 - 0: (empty) - 1: R_STATEMENT_LIST@0..40 - 0: R_EXPRESSION_STATEMENT@0..8 - 0: R_REPEAT_STATEMENT@0..8 - 0: REPEAT_KW@0..7 "repeat" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@7..8 - 0: R_DOUBLE_LITERAL@7..8 "1" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@8..20 - 0: R_REPEAT_STATEMENT@8..20 - 0: REPEAT_KW@8..17 "repeat" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: R_BRACED_STATEMENTS@17..20 - 0: L_CURLY@17..18 "{" [] [] - 1: R_STATEMENT_LIST@18..18 - 2: R_CURLY@18..20 "}" [Newline("\n")] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@20..40 - 0: R_REPEAT_STATEMENT@20..40 - 0: REPEAT_KW@20..29 "repeat" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: R_BRACED_STATEMENTS@29..40 - 0: L_CURLY@29..30 "{" [] [] - 1: R_STATEMENT_LIST@30..38 - 0: R_EXPRESSION_STATEMENT@30..38 - 0: R_BINARY_EXPRESSION@30..38 - 0: R_DOUBLE_VALUE@30..35 - 0: R_DOUBLE_LITERAL@30..35 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")] - 1: PLUS@35..37 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@37..38 - 0: R_DOUBLE_LITERAL@37..38 "1" [] [] - 1: (empty) - 2: R_CURLY@38..40 "}" [Newline("\n")] [] - 1: (empty) - 2: EOF@40..41 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-01.R b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-01.R deleted file mode 100644 index 106a966b..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-01.R +++ /dev/null @@ -1,2 +0,0 @@ -# https://github.com/posit-dev/air/issues/238 -1; diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-01.R.snap b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-01.R.snap deleted file mode 100644 index b27be4c2..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-01.R.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -# https://github.com/posit-dev/air/issues/238 -1; - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..48 "1" [Comments("# https://github.com/ ..."), Newline("\n")] [Whitespace(";")], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@48..49 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..49 - 0: (empty) - 1: R_STATEMENT_LIST@0..48 - 0: R_EXPRESSION_STATEMENT@0..48 - 0: R_DOUBLE_VALUE@0..48 - 0: R_DOUBLE_LITERAL@0..48 "1" [Comments("# https://github.com/ ..."), Newline("\n")] [Whitespace(";")] - 1: (empty) - 2: EOF@48..49 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-02.R b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-02.R deleted file mode 100644 index bdf81218..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-02.R +++ /dev/null @@ -1 +0,0 @@ -1;; diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-02.R.snap b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-02.R.snap deleted file mode 100644 index b91cb0c1..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-02.R.snap +++ /dev/null @@ -1,42 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -1;; - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..3 "1" [] [Whitespace(";;")], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@3..4 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..4 - 0: (empty) - 1: R_STATEMENT_LIST@0..3 - 0: R_EXPRESSION_STATEMENT@0..3 - 0: R_DOUBLE_VALUE@0..3 - 0: R_DOUBLE_LITERAL@0..3 "1" [] [Whitespace(";;")] - 1: (empty) - 2: EOF@3..4 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-03.R b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-03.R deleted file mode 100644 index 5c099ac5..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-03.R +++ /dev/null @@ -1,3 +0,0 @@ -1 - -; diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-03.R.snap b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-03.R.snap deleted file mode 100644 index ba315843..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-end-of-file-03.R.snap +++ /dev/null @@ -1,44 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -1 - -; - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@1..5 "" [Newline("\n"), Newline("\n"), Whitespace(";"), Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..5 - 0: (empty) - 1: R_STATEMENT_LIST@0..1 - 0: R_EXPRESSION_STATEMENT@0..1 - 0: R_DOUBLE_VALUE@0..1 - 0: R_DOUBLE_LITERAL@0..1 "1" [] [] - 1: (empty) - 2: EOF@1..5 "" [Newline("\n"), Newline("\n"), Whitespace(";"), Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-01.R b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-01.R deleted file mode 100644 index c203e438..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-01.R +++ /dev/null @@ -1,2 +0,0 @@ -; -1 diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-01.R.snap b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-01.R.snap deleted file mode 100644 index f7db41e7..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-01.R.snap +++ /dev/null @@ -1,43 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -; -1 - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..3 "1" [Whitespace(";"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@3..4 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..4 - 0: (empty) - 1: R_STATEMENT_LIST@0..3 - 0: R_EXPRESSION_STATEMENT@0..3 - 0: R_DOUBLE_VALUE@0..3 - 0: R_DOUBLE_LITERAL@0..3 "1" [Whitespace(";"), Newline("\n")] [] - 1: (empty) - 2: EOF@3..4 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-02.R b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-02.R deleted file mode 100644 index 17d762e3..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-02.R +++ /dev/null @@ -1 +0,0 @@ -;1 diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-02.R.snap b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-02.R.snap deleted file mode 100644 index ec97c801..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolon-start-of-file-02.R.snap +++ /dev/null @@ -1,42 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -;1 - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..2 "1" [Whitespace(";")] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@2..3 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..3 - 0: (empty) - 1: R_STATEMENT_LIST@0..2 - 0: R_EXPRESSION_STATEMENT@0..2 - 0: R_DOUBLE_VALUE@0..2 - 0: R_DOUBLE_LITERAL@0..2 "1" [Whitespace(";")] [] - 1: (empty) - 2: EOF@2..3 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolons.R b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolons.R deleted file mode 100644 index 9acbe439..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolons.R +++ /dev/null @@ -1,16 +0,0 @@ -1;2 - -1 ; 2 - -1;;2 - -{ - 1; -} - -{ - 1; 2 -} - -1;# comment -1; # comment diff --git a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolons.R.snap b/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolons.R.snap deleted file mode 100644 index 66d34985..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/semicolons/semicolons.R.snap +++ /dev/null @@ -1,187 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -1;2 - -1 ; 2 - -1;;2 - -{ - 1; -} - -{ - 1; 2 -} - -1;# comment -1; # comment - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..2 "1" [] [Whitespace(";")], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@2..3 "2" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@3..9 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ; ")], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@9..10 "2" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@10..15 "1" [Newline("\n"), Newline("\n")] [Whitespace(";;")], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@15..16 "2" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@16..19 "{" [Newline("\n"), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@19..26 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(";")], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@26..28 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@28..31 "{" [Newline("\n"), Newline("\n")] [], - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@31..39 "1" [Newline("\n"), Whitespace(" ")] [Whitespace("; ")], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@39..40 "2" [] [], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@40..42 "}" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@42..55 "1" [Newline("\n"), Newline("\n")] [Whitespace(";"), Comments("# comment")], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@55..68 "1" [Newline("\n")] [Whitespace("; "), Comments("# comment")], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@68..69 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..69 - 0: (empty) - 1: R_STATEMENT_LIST@0..68 - 0: R_EXPRESSION_STATEMENT@0..2 - 0: R_DOUBLE_VALUE@0..2 - 0: R_DOUBLE_LITERAL@0..2 "1" [] [Whitespace(";")] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@2..3 - 0: R_DOUBLE_VALUE@2..3 - 0: R_DOUBLE_LITERAL@2..3 "2" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@3..9 - 0: R_DOUBLE_VALUE@3..9 - 0: R_DOUBLE_LITERAL@3..9 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ; ")] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@9..10 - 0: R_DOUBLE_VALUE@9..10 - 0: R_DOUBLE_LITERAL@9..10 "2" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@10..15 - 0: R_DOUBLE_VALUE@10..15 - 0: R_DOUBLE_LITERAL@10..15 "1" [Newline("\n"), Newline("\n")] [Whitespace(";;")] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@15..16 - 0: R_DOUBLE_VALUE@15..16 - 0: R_DOUBLE_LITERAL@15..16 "2" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@16..28 - 0: R_BRACED_STATEMENTS@16..28 - 0: L_CURLY@16..19 "{" [Newline("\n"), Newline("\n")] [] - 1: R_STATEMENT_LIST@19..26 - 0: R_EXPRESSION_STATEMENT@19..26 - 0: R_DOUBLE_VALUE@19..26 - 0: R_DOUBLE_LITERAL@19..26 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(";")] - 1: (empty) - 2: R_CURLY@26..28 "}" [Newline("\n")] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@28..42 - 0: R_BRACED_STATEMENTS@28..42 - 0: L_CURLY@28..31 "{" [Newline("\n"), Newline("\n")] [] - 1: R_STATEMENT_LIST@31..40 - 0: R_EXPRESSION_STATEMENT@31..39 - 0: R_DOUBLE_VALUE@31..39 - 0: R_DOUBLE_LITERAL@31..39 "1" [Newline("\n"), Whitespace(" ")] [Whitespace("; ")] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@39..40 - 0: R_DOUBLE_VALUE@39..40 - 0: R_DOUBLE_LITERAL@39..40 "2" [] [] - 1: (empty) - 2: R_CURLY@40..42 "}" [Newline("\n")] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@42..55 - 0: R_DOUBLE_VALUE@42..55 - 0: R_DOUBLE_LITERAL@42..55 "1" [Newline("\n"), Newline("\n")] [Whitespace(";"), Comments("# comment")] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@55..68 - 0: R_DOUBLE_VALUE@55..68 - 0: R_DOUBLE_LITERAL@55..68 "1" [Newline("\n")] [Whitespace("; "), Comments("# comment")] - 1: (empty) - 2: EOF@68..69 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/subset2.R b/crates/air_r_parser/tests/snapshots/ok/subset2.R deleted file mode 100644 index e9e13a0c..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/subset2.R +++ /dev/null @@ -1,41 +0,0 @@ -fn -fn[[a]] -fn[[a, b]] -fn[[...]] -fn[[a, ..., b]] - -fn[[a = 1]] -fn[[a = ]] - -fn[[a = 1, b = 2]] -fn[[a = , b = 2]] - -fn[[... = 1]] -fn[[... =]] - -fn[["arg" = 1]] -fn[["arg" =]] - -# Comma tests -fn[[,]] -fn[[,,,]] -fn[[,,a,,b,,]] - -# Comment tests -fn[[ - # comment -]] -fn[[, - # comment -,]] -fn[[ - a, # comment1 - b # comment2 -]] -fn[[ - a # comment1 - = # comment2 - 1 # comment3 -]] - -{expr}[[a = 1]] diff --git a/crates/air_r_parser/tests/snapshots/ok/unary_expressions.R b/crates/air_r_parser/tests/snapshots/ok/unary_expressions.R deleted file mode 100644 index f1af856f..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/unary_expressions.R +++ /dev/null @@ -1,18 +0,0 @@ -+1 --1 -~1 -?1 -!1 - -+ -1 - -+ # Comment -1 - -+ -# Comment -+ # Comment -1 - -1 + ++1 diff --git a/crates/air_r_parser/tests/snapshots/ok/unary_expressions.R.snap b/crates/air_r_parser/tests/snapshots/ok/unary_expressions.R.snap deleted file mode 100644 index 8ccb35aa..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/unary_expressions.R.snap +++ /dev/null @@ -1,204 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -+1 --1 -~1 -?1 -!1 - -+ -1 - -+ # Comment -1 - -+ -# Comment -+ # Comment -1 - -1 + ++1 - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RUnaryExpression { - operator: PLUS@0..1 "+" [] [], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@1..2 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RUnaryExpression { - operator: MINUS@2..4 "-" [Newline("\n")] [], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@4..5 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RUnaryExpression { - operator: TILDE@5..7 "~" [Newline("\n")] [], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@7..8 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RUnaryExpression { - operator: WAT@8..10 "?" [Newline("\n")] [], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@10..11 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RUnaryExpression { - operator: BANG@11..13 "!" [Newline("\n")] [], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@13..14 "1" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RUnaryExpression { - operator: PLUS@14..17 "+" [Newline("\n"), Newline("\n")] [], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@17..19 "1" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RUnaryExpression { - operator: PLUS@19..32 "+" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# Comment")], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@32..34 "1" [Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RUnaryExpression { - operator: PLUS@34..37 "+" [Newline("\n"), Newline("\n")] [], - argument: RUnaryExpression { - operator: PLUS@37..59 "+" [Newline("\n"), Comments("# Comment"), Newline("\n")] [Whitespace(" "), Comments("# Comment")], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@59..61 "1" [Newline("\n")] [], - }, - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBinaryExpression { - left: RDoubleValue { - value_token: R_DOUBLE_LITERAL@61..65 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ")], - }, - operator: PLUS@65..67 "+" [] [Whitespace(" ")], - right: RUnaryExpression { - operator: PLUS@67..68 "+" [] [], - argument: RUnaryExpression { - operator: PLUS@68..69 "+" [] [], - argument: RDoubleValue { - value_token: R_DOUBLE_LITERAL@69..70 "1" [] [], - }, - }, - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@70..71 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..71 - 0: (empty) - 1: R_STATEMENT_LIST@0..70 - 0: R_EXPRESSION_STATEMENT@0..2 - 0: R_UNARY_EXPRESSION@0..2 - 0: PLUS@0..1 "+" [] [] - 1: R_DOUBLE_VALUE@1..2 - 0: R_DOUBLE_LITERAL@1..2 "1" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@2..5 - 0: R_UNARY_EXPRESSION@2..5 - 0: MINUS@2..4 "-" [Newline("\n")] [] - 1: R_DOUBLE_VALUE@4..5 - 0: R_DOUBLE_LITERAL@4..5 "1" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@5..8 - 0: R_UNARY_EXPRESSION@5..8 - 0: TILDE@5..7 "~" [Newline("\n")] [] - 1: R_DOUBLE_VALUE@7..8 - 0: R_DOUBLE_LITERAL@7..8 "1" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@8..11 - 0: R_UNARY_EXPRESSION@8..11 - 0: WAT@8..10 "?" [Newline("\n")] [] - 1: R_DOUBLE_VALUE@10..11 - 0: R_DOUBLE_LITERAL@10..11 "1" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@11..14 - 0: R_UNARY_EXPRESSION@11..14 - 0: BANG@11..13 "!" [Newline("\n")] [] - 1: R_DOUBLE_VALUE@13..14 - 0: R_DOUBLE_LITERAL@13..14 "1" [] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@14..19 - 0: R_UNARY_EXPRESSION@14..19 - 0: PLUS@14..17 "+" [Newline("\n"), Newline("\n")] [] - 1: R_DOUBLE_VALUE@17..19 - 0: R_DOUBLE_LITERAL@17..19 "1" [Newline("\n")] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@19..34 - 0: R_UNARY_EXPRESSION@19..34 - 0: PLUS@19..32 "+" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# Comment")] - 1: R_DOUBLE_VALUE@32..34 - 0: R_DOUBLE_LITERAL@32..34 "1" [Newline("\n")] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@34..61 - 0: R_UNARY_EXPRESSION@34..61 - 0: PLUS@34..37 "+" [Newline("\n"), Newline("\n")] [] - 1: R_UNARY_EXPRESSION@37..61 - 0: PLUS@37..59 "+" [Newline("\n"), Comments("# Comment"), Newline("\n")] [Whitespace(" "), Comments("# Comment")] - 1: R_DOUBLE_VALUE@59..61 - 0: R_DOUBLE_LITERAL@59..61 "1" [Newline("\n")] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@61..70 - 0: R_BINARY_EXPRESSION@61..70 - 0: R_DOUBLE_VALUE@61..65 - 0: R_DOUBLE_LITERAL@61..65 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: PLUS@65..67 "+" [] [Whitespace(" ")] - 2: R_UNARY_EXPRESSION@67..70 - 0: PLUS@67..68 "+" [] [] - 1: R_UNARY_EXPRESSION@68..70 - 0: PLUS@68..69 "+" [] [] - 1: R_DOUBLE_VALUE@69..70 - 0: R_DOUBLE_LITERAL@69..70 "1" [] [] - 1: (empty) - 2: EOF@70..71 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/value/complex_value.R b/crates/air_r_parser/tests/snapshots/ok/value/complex_value.R deleted file mode 100644 index 212a47e9..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/value/complex_value.R +++ /dev/null @@ -1,7 +0,0 @@ -1i -2.5i -1e6i -0x123Fi - -# https://github.com/r-lib/tree-sitter-r/pull/198 -.1i diff --git a/crates/air_r_parser/tests/snapshots/ok/value/complex_value.R.snap b/crates/air_r_parser/tests/snapshots/ok/value/complex_value.R.snap deleted file mode 100644 index 389e1451..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/value/complex_value.R.snap +++ /dev/null @@ -1,88 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -1i -2.5i -1e6i -0x123Fi - -# https://github.com/r-lib/tree-sitter-r/pull/198 -.1i - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RComplexValue { - value_token: R_COMPLEX_LITERAL@0..2 "1i" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RComplexValue { - value_token: R_COMPLEX_LITERAL@2..7 "2.5i" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RComplexValue { - value_token: R_COMPLEX_LITERAL@7..12 "1e6i" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RComplexValue { - value_token: R_COMPLEX_LITERAL@12..20 "0x123Fi" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RComplexValue { - value_token: R_COMPLEX_LITERAL@20..75 ".1i" [Newline("\n"), Newline("\n"), Comments("# https://github.com/ ..."), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@75..76 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..76 - 0: (empty) - 1: R_STATEMENT_LIST@0..75 - 0: R_EXPRESSION_STATEMENT@0..2 - 0: R_COMPLEX_VALUE@0..2 - 0: R_COMPLEX_LITERAL@0..2 "1i" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@2..7 - 0: R_COMPLEX_VALUE@2..7 - 0: R_COMPLEX_LITERAL@2..7 "2.5i" [Newline("\n")] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@7..12 - 0: R_COMPLEX_VALUE@7..12 - 0: R_COMPLEX_LITERAL@7..12 "1e6i" [Newline("\n")] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@12..20 - 0: R_COMPLEX_VALUE@12..20 - 0: R_COMPLEX_LITERAL@12..20 "0x123Fi" [Newline("\n")] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@20..75 - 0: R_COMPLEX_VALUE@20..75 - 0: R_COMPLEX_LITERAL@20..75 ".1i" [Newline("\n"), Newline("\n"), Comments("# https://github.com/ ..."), Newline("\n")] [] - 1: (empty) - 2: EOF@75..76 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/value/double_value.R b/crates/air_r_parser/tests/snapshots/ok/value/double_value.R deleted file mode 100644 index c26d2c4e..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/value/double_value.R +++ /dev/null @@ -1,37 +0,0 @@ -1 -2.5 -1e6 -0x123F - -# https://github.com/r-lib/tree-sitter-r/pull/198 -.1 - -# Hexadecimal -# `x` vs `X` -0x123 -0X123 -# Numbers and letters -0xDEAD -0XDEAD -0x1f2F3 -0X1f2F3 -# `p` vs `P` -0x0p0 -0x0P0 -0x0p123 -0x0P123 -# `+` and `-` -0x0p+0 -0x0p-0 -0x0p+123 -0x0p-123 -# Decimal point -0x. -0x1. -0x.1 -0x1.1 -# Decimal point with exponent -0x.p1 -0x1.p1 -0x.1p1 -0x1.1p1 \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/ok/value/double_value.R.snap b/crates/air_r_parser/tests/snapshots/ok/value/double_value.R.snap deleted file mode 100644 index ebc3d42e..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/value/double_value.R.snap +++ /dev/null @@ -1,337 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -1 -2.5 -1e6 -0x123F - -# https://github.com/r-lib/tree-sitter-r/pull/198 -.1 - -# Hexadecimal -# `x` vs `X` -0x123 -0X123 -# Numbers and letters -0xDEAD -0XDEAD -0x1f2F3 -0X1f2F3 -# `p` vs `P` -0x0p0 -0x0P0 -0x0p123 -0x0P123 -# `+` and `-` -0x0p+0 -0x0p-0 -0x0p+123 -0x0p-123 -# Decimal point -0x. -0x1. -0x.1 -0x1.1 -# Decimal point with exponent -0x.p1 -0x1.p1 -0x.1p1 -0x1.1p1 -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@1..5 "2.5" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@5..9 "1e6" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@9..16 "0x123F" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@16..70 ".1" [Newline("\n"), Newline("\n"), Comments("# https://github.com/ ..."), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@70..104 "0x123" [Newline("\n"), Newline("\n"), Comments("# Hexadecimal"), Newline("\n"), Comments("# `x` vs `X`"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@104..110 "0X123" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@110..139 "0xDEAD" [Newline("\n"), Comments("# Numbers and letters"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@139..146 "0XDEAD" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@146..154 "0x1f2F3" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@154..162 "0X1f2F3" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@162..181 "0x0p0" [Newline("\n"), Comments("# `p` vs `P`"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@181..187 "0x0P0" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@187..195 "0x0p123" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@195..203 "0x0P123" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@203..224 "0x0p+0" [Newline("\n"), Comments("# `+` and `-`"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@224..231 "0x0p-0" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@231..240 "0x0p+123" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@240..249 "0x0p-123" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@249..269 "0x." [Newline("\n"), Comments("# Decimal point"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@269..274 "0x1." [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@274..279 "0x.1" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@279..285 "0x1.1" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@285..321 "0x.p1" [Newline("\n"), Comments("# Decimal point with ..."), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@321..328 "0x1.p1" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@328..335 "0x.1p1" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@335..343 "0x1.1p1" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@343..343 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..343 - 0: (empty) - 1: R_STATEMENT_LIST@0..343 - 0: R_EXPRESSION_STATEMENT@0..1 - 0: R_DOUBLE_VALUE@0..1 - 0: R_DOUBLE_LITERAL@0..1 "1" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@1..5 - 0: R_DOUBLE_VALUE@1..5 - 0: R_DOUBLE_LITERAL@1..5 "2.5" [Newline("\n")] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@5..9 - 0: R_DOUBLE_VALUE@5..9 - 0: R_DOUBLE_LITERAL@5..9 "1e6" [Newline("\n")] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@9..16 - 0: R_DOUBLE_VALUE@9..16 - 0: R_DOUBLE_LITERAL@9..16 "0x123F" [Newline("\n")] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@16..70 - 0: R_DOUBLE_VALUE@16..70 - 0: R_DOUBLE_LITERAL@16..70 ".1" [Newline("\n"), Newline("\n"), Comments("# https://github.com/ ..."), Newline("\n")] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@70..104 - 0: R_DOUBLE_VALUE@70..104 - 0: R_DOUBLE_LITERAL@70..104 "0x123" [Newline("\n"), Newline("\n"), Comments("# Hexadecimal"), Newline("\n"), Comments("# `x` vs `X`"), Newline("\n")] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@104..110 - 0: R_DOUBLE_VALUE@104..110 - 0: R_DOUBLE_LITERAL@104..110 "0X123" [Newline("\n")] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@110..139 - 0: R_DOUBLE_VALUE@110..139 - 0: R_DOUBLE_LITERAL@110..139 "0xDEAD" [Newline("\n"), Comments("# Numbers and letters"), Newline("\n")] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@139..146 - 0: R_DOUBLE_VALUE@139..146 - 0: R_DOUBLE_LITERAL@139..146 "0XDEAD" [Newline("\n")] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@146..154 - 0: R_DOUBLE_VALUE@146..154 - 0: R_DOUBLE_LITERAL@146..154 "0x1f2F3" [Newline("\n")] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@154..162 - 0: R_DOUBLE_VALUE@154..162 - 0: R_DOUBLE_LITERAL@154..162 "0X1f2F3" [Newline("\n")] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@162..181 - 0: R_DOUBLE_VALUE@162..181 - 0: R_DOUBLE_LITERAL@162..181 "0x0p0" [Newline("\n"), Comments("# `p` vs `P`"), Newline("\n")] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@181..187 - 0: R_DOUBLE_VALUE@181..187 - 0: R_DOUBLE_LITERAL@181..187 "0x0P0" [Newline("\n")] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@187..195 - 0: R_DOUBLE_VALUE@187..195 - 0: R_DOUBLE_LITERAL@187..195 "0x0p123" [Newline("\n")] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@195..203 - 0: R_DOUBLE_VALUE@195..203 - 0: R_DOUBLE_LITERAL@195..203 "0x0P123" [Newline("\n")] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@203..224 - 0: R_DOUBLE_VALUE@203..224 - 0: R_DOUBLE_LITERAL@203..224 "0x0p+0" [Newline("\n"), Comments("# `+` and `-`"), Newline("\n")] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@224..231 - 0: R_DOUBLE_VALUE@224..231 - 0: R_DOUBLE_LITERAL@224..231 "0x0p-0" [Newline("\n")] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@231..240 - 0: R_DOUBLE_VALUE@231..240 - 0: R_DOUBLE_LITERAL@231..240 "0x0p+123" [Newline("\n")] [] - 1: (empty) - 18: R_EXPRESSION_STATEMENT@240..249 - 0: R_DOUBLE_VALUE@240..249 - 0: R_DOUBLE_LITERAL@240..249 "0x0p-123" [Newline("\n")] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@249..269 - 0: R_DOUBLE_VALUE@249..269 - 0: R_DOUBLE_LITERAL@249..269 "0x." [Newline("\n"), Comments("# Decimal point"), Newline("\n")] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@269..274 - 0: R_DOUBLE_VALUE@269..274 - 0: R_DOUBLE_LITERAL@269..274 "0x1." [Newline("\n")] [] - 1: (empty) - 21: R_EXPRESSION_STATEMENT@274..279 - 0: R_DOUBLE_VALUE@274..279 - 0: R_DOUBLE_LITERAL@274..279 "0x.1" [Newline("\n")] [] - 1: (empty) - 22: R_EXPRESSION_STATEMENT@279..285 - 0: R_DOUBLE_VALUE@279..285 - 0: R_DOUBLE_LITERAL@279..285 "0x1.1" [Newline("\n")] [] - 1: (empty) - 23: R_EXPRESSION_STATEMENT@285..321 - 0: R_DOUBLE_VALUE@285..321 - 0: R_DOUBLE_LITERAL@285..321 "0x.p1" [Newline("\n"), Comments("# Decimal point with ..."), Newline("\n")] [] - 1: (empty) - 24: R_EXPRESSION_STATEMENT@321..328 - 0: R_DOUBLE_VALUE@321..328 - 0: R_DOUBLE_LITERAL@321..328 "0x1.p1" [Newline("\n")] [] - 1: (empty) - 25: R_EXPRESSION_STATEMENT@328..335 - 0: R_DOUBLE_VALUE@328..335 - 0: R_DOUBLE_LITERAL@328..335 "0x.1p1" [Newline("\n")] [] - 1: (empty) - 26: R_EXPRESSION_STATEMENT@335..343 - 0: R_DOUBLE_VALUE@335..343 - 0: R_DOUBLE_LITERAL@335..343 "0x1.1p1" [Newline("\n")] [] - 1: (empty) - 2: EOF@343..343 "" [] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/value/integer_value.R b/crates/air_r_parser/tests/snapshots/ok/value/integer_value.R deleted file mode 100644 index 7bd3aa3c..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/value/integer_value.R +++ /dev/null @@ -1,35 +0,0 @@ -1L -1e5L - -# https://github.com/r-lib/tree-sitter-r/pull/198 -.1L - -# Hexadecimal -# `x` vs `X` -0x123L -0X123L -# Numbers and letters -0xDEADL -0XDEADL -0x1f2F3L -0X1f2F3L -# `p` vs `P` -0x0p0L -0x0P0L -0x0p123L -0x0P123L -# `+` and `-` -0x0p+0L -0x0p-0L -0x0p+123L -0x0p-123L -# Decimal point -0x.L -0x1.L -0x.1L -0x1.1L -# Decimal point with exponent -0x.p1L -0x1.p1L -0x.1p1L -0x1.1p1L \ No newline at end of file diff --git a/crates/air_r_parser/tests/snapshots/ok/value/integer_value.R.snap b/crates/air_r_parser/tests/snapshots/ok/value/integer_value.R.snap deleted file mode 100644 index 2df3acf3..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/value/integer_value.R.snap +++ /dev/null @@ -1,315 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -1L -1e5L - -# https://github.com/r-lib/tree-sitter-r/pull/198 -.1L - -# Hexadecimal -# `x` vs `X` -0x123L -0X123L -# Numbers and letters -0xDEADL -0XDEADL -0x1f2F3L -0X1f2F3L -# `p` vs `P` -0x0p0L -0x0P0L -0x0p123L -0x0P123L -# `+` and `-` -0x0p+0L -0x0p-0L -0x0p+123L -0x0p-123L -# Decimal point -0x.L -0x1.L -0x.1L -0x1.1L -# Decimal point with exponent -0x.p1L -0x1.p1L -0x.1p1L -0x1.1p1L -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@0..2 "1L" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@2..7 "1e5L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@7..62 ".1L" [Newline("\n"), Newline("\n"), Comments("# https://github.com/ ..."), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@62..97 "0x123L" [Newline("\n"), Newline("\n"), Comments("# Hexadecimal"), Newline("\n"), Comments("# `x` vs `X`"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@97..104 "0X123L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@104..134 "0xDEADL" [Newline("\n"), Comments("# Numbers and letters"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@134..142 "0XDEADL" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@142..151 "0x1f2F3L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@151..160 "0X1f2F3L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@160..180 "0x0p0L" [Newline("\n"), Comments("# `p` vs `P`"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@180..187 "0x0P0L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@187..196 "0x0p123L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@196..205 "0x0P123L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@205..227 "0x0p+0L" [Newline("\n"), Comments("# `+` and `-`"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@227..235 "0x0p-0L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@235..245 "0x0p+123L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@245..255 "0x0p-123L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@255..276 "0x.L" [Newline("\n"), Comments("# Decimal point"), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@276..282 "0x1.L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@282..288 "0x.1L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@288..295 "0x1.1L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@295..332 "0x.p1L" [Newline("\n"), Comments("# Decimal point with ..."), Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@332..340 "0x1.p1L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@340..348 "0x.1p1L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RIntegerValue { - value_token: R_INTEGER_LITERAL@348..357 "0x1.1p1L" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@357..357 "" [] [], -} -``` - -## CST - -``` -0: R_ROOT@0..357 - 0: (empty) - 1: R_STATEMENT_LIST@0..357 - 0: R_EXPRESSION_STATEMENT@0..2 - 0: R_INTEGER_VALUE@0..2 - 0: R_INTEGER_LITERAL@0..2 "1L" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@2..7 - 0: R_INTEGER_VALUE@2..7 - 0: R_INTEGER_LITERAL@2..7 "1e5L" [Newline("\n")] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@7..62 - 0: R_INTEGER_VALUE@7..62 - 0: R_INTEGER_LITERAL@7..62 ".1L" [Newline("\n"), Newline("\n"), Comments("# https://github.com/ ..."), Newline("\n")] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@62..97 - 0: R_INTEGER_VALUE@62..97 - 0: R_INTEGER_LITERAL@62..97 "0x123L" [Newline("\n"), Newline("\n"), Comments("# Hexadecimal"), Newline("\n"), Comments("# `x` vs `X`"), Newline("\n")] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@97..104 - 0: R_INTEGER_VALUE@97..104 - 0: R_INTEGER_LITERAL@97..104 "0X123L" [Newline("\n")] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@104..134 - 0: R_INTEGER_VALUE@104..134 - 0: R_INTEGER_LITERAL@104..134 "0xDEADL" [Newline("\n"), Comments("# Numbers and letters"), Newline("\n")] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@134..142 - 0: R_INTEGER_VALUE@134..142 - 0: R_INTEGER_LITERAL@134..142 "0XDEADL" [Newline("\n")] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@142..151 - 0: R_INTEGER_VALUE@142..151 - 0: R_INTEGER_LITERAL@142..151 "0x1f2F3L" [Newline("\n")] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@151..160 - 0: R_INTEGER_VALUE@151..160 - 0: R_INTEGER_LITERAL@151..160 "0X1f2F3L" [Newline("\n")] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@160..180 - 0: R_INTEGER_VALUE@160..180 - 0: R_INTEGER_LITERAL@160..180 "0x0p0L" [Newline("\n"), Comments("# `p` vs `P`"), Newline("\n")] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@180..187 - 0: R_INTEGER_VALUE@180..187 - 0: R_INTEGER_LITERAL@180..187 "0x0P0L" [Newline("\n")] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@187..196 - 0: R_INTEGER_VALUE@187..196 - 0: R_INTEGER_LITERAL@187..196 "0x0p123L" [Newline("\n")] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@196..205 - 0: R_INTEGER_VALUE@196..205 - 0: R_INTEGER_LITERAL@196..205 "0x0P123L" [Newline("\n")] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@205..227 - 0: R_INTEGER_VALUE@205..227 - 0: R_INTEGER_LITERAL@205..227 "0x0p+0L" [Newline("\n"), Comments("# `+` and `-`"), Newline("\n")] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@227..235 - 0: R_INTEGER_VALUE@227..235 - 0: R_INTEGER_LITERAL@227..235 "0x0p-0L" [Newline("\n")] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@235..245 - 0: R_INTEGER_VALUE@235..245 - 0: R_INTEGER_LITERAL@235..245 "0x0p+123L" [Newline("\n")] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@245..255 - 0: R_INTEGER_VALUE@245..255 - 0: R_INTEGER_LITERAL@245..255 "0x0p-123L" [Newline("\n")] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@255..276 - 0: R_INTEGER_VALUE@255..276 - 0: R_INTEGER_LITERAL@255..276 "0x.L" [Newline("\n"), Comments("# Decimal point"), Newline("\n")] [] - 1: (empty) - 18: R_EXPRESSION_STATEMENT@276..282 - 0: R_INTEGER_VALUE@276..282 - 0: R_INTEGER_LITERAL@276..282 "0x1.L" [Newline("\n")] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@282..288 - 0: R_INTEGER_VALUE@282..288 - 0: R_INTEGER_LITERAL@282..288 "0x.1L" [Newline("\n")] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@288..295 - 0: R_INTEGER_VALUE@288..295 - 0: R_INTEGER_LITERAL@288..295 "0x1.1L" [Newline("\n")] [] - 1: (empty) - 21: R_EXPRESSION_STATEMENT@295..332 - 0: R_INTEGER_VALUE@295..332 - 0: R_INTEGER_LITERAL@295..332 "0x.p1L" [Newline("\n"), Comments("# Decimal point with ..."), Newline("\n")] [] - 1: (empty) - 22: R_EXPRESSION_STATEMENT@332..340 - 0: R_INTEGER_VALUE@332..340 - 0: R_INTEGER_LITERAL@332..340 "0x1.p1L" [Newline("\n")] [] - 1: (empty) - 23: R_EXPRESSION_STATEMENT@340..348 - 0: R_INTEGER_VALUE@340..348 - 0: R_INTEGER_LITERAL@340..348 "0x.1p1L" [Newline("\n")] [] - 1: (empty) - 24: R_EXPRESSION_STATEMENT@348..357 - 0: R_INTEGER_VALUE@348..357 - 0: R_INTEGER_LITERAL@348..357 "0x1.1p1L" [Newline("\n")] [] - 1: (empty) - 2: EOF@357..357 "" [] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/value/string_value.R b/crates/air_r_parser/tests/snapshots/ok/value/string_value.R deleted file mode 100644 index 9343f245..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/value/string_value.R +++ /dev/null @@ -1,33 +0,0 @@ -"hi there!" -"\"" -'hi there!' -'\'' -"'" -"multiline -string" -r"(")" -r"("")" -r"(')" -r"('')" -r"-(-)-" -r"-()-)-" -r"--()-")--" -r"( () )" -r"(())" -r"( ())" -r"(() )" -r"-())-" -r"-(())-)-" -r"(raw -string -)" -r"("some raw string -business")" -R"(upper)" -r'(single)' -R'(upper single)' -r"[brackets]" -r"{braces}" -"" -'' -r"()" diff --git a/crates/air_r_parser/tests/snapshots/ok/value/string_value.R.snap b/crates/air_r_parser/tests/snapshots/ok/value/string_value.R.snap deleted file mode 100644 index 8ad6040d..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/value/string_value.R.snap +++ /dev/null @@ -1,354 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -"hi there!" -"\"" -'hi there!' -'\'' -"'" -"multiline -string" -r"(")" -r"("")" -r"(')" -r"('')" -r"-(-)-" -r"-()-)-" -r"--()-")--" -r"( () )" -r"(())" -r"( ())" -r"(() )" -r"-())-" -r"-(())-)-" -r"(raw -string -)" -r"("some raw string -business")" -R"(upper)" -r'(single)' -R'(upper single)' -r"[brackets]" -r"{braces}" -"" -'' -r"()" - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@0..11 "\"hi there!\"" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@11..16 "\"\\\"\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_SINGLE_QUOTED_STRING_LITERAL@16..28 "'hi there!'" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_SINGLE_QUOTED_STRING_LITERAL@28..33 "'\\''" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@33..37 "\"'\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@37..56 "\"multiline\nstring\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@56..63 "r\"(\")\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@63..71 "r\"(\"\")\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@71..78 "r\"(')\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@78..86 "r\"('')\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@86..95 "r\"-(-)-\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@95..105 "r\"-()-)-\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@105..118 "r\"--()-\")--\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@118..128 "r\"( () )\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@128..136 "r\"(())\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@136..145 "r\"( ())\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@145..154 "r\"(() )\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@154..163 "r\"-())-\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@163..175 "r\"-(())-)-\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@175..192 "r\"(raw\nstring\n)\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@192..224 "r\"(\"some raw string\nbusiness\")\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@224..235 "R\"(upper)\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@235..247 "r'(single)'" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@247..265 "R'(upper single)'" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@265..279 "r\"[brackets]\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@279..291 "r\"{braces}\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_DOUBLE_QUOTED_STRING_LITERAL@291..294 "\"\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_SINGLE_QUOTED_STRING_LITERAL@294..297 "''" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RStringValue { - value: R_RAW_STRING_LITERAL@297..303 "r\"()\"" [Newline("\n")] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@303..304 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..304 - 0: (empty) - 1: R_STATEMENT_LIST@0..303 - 0: R_EXPRESSION_STATEMENT@0..11 - 0: R_STRING_VALUE@0..11 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@0..11 "\"hi there!\"" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@11..16 - 0: R_STRING_VALUE@11..16 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@11..16 "\"\\\"\"" [Newline("\n")] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@16..28 - 0: R_STRING_VALUE@16..28 - 0: R_SINGLE_QUOTED_STRING_LITERAL@16..28 "'hi there!'" [Newline("\n")] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@28..33 - 0: R_STRING_VALUE@28..33 - 0: R_SINGLE_QUOTED_STRING_LITERAL@28..33 "'\\''" [Newline("\n")] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@33..37 - 0: R_STRING_VALUE@33..37 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@33..37 "\"'\"" [Newline("\n")] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@37..56 - 0: R_STRING_VALUE@37..56 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@37..56 "\"multiline\nstring\"" [Newline("\n")] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@56..63 - 0: R_STRING_VALUE@56..63 - 0: R_RAW_STRING_LITERAL@56..63 "r\"(\")\"" [Newline("\n")] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@63..71 - 0: R_STRING_VALUE@63..71 - 0: R_RAW_STRING_LITERAL@63..71 "r\"(\"\")\"" [Newline("\n")] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@71..78 - 0: R_STRING_VALUE@71..78 - 0: R_RAW_STRING_LITERAL@71..78 "r\"(')\"" [Newline("\n")] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@78..86 - 0: R_STRING_VALUE@78..86 - 0: R_RAW_STRING_LITERAL@78..86 "r\"('')\"" [Newline("\n")] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@86..95 - 0: R_STRING_VALUE@86..95 - 0: R_RAW_STRING_LITERAL@86..95 "r\"-(-)-\"" [Newline("\n")] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@95..105 - 0: R_STRING_VALUE@95..105 - 0: R_RAW_STRING_LITERAL@95..105 "r\"-()-)-\"" [Newline("\n")] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@105..118 - 0: R_STRING_VALUE@105..118 - 0: R_RAW_STRING_LITERAL@105..118 "r\"--()-\")--\"" [Newline("\n")] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@118..128 - 0: R_STRING_VALUE@118..128 - 0: R_RAW_STRING_LITERAL@118..128 "r\"( () )\"" [Newline("\n")] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@128..136 - 0: R_STRING_VALUE@128..136 - 0: R_RAW_STRING_LITERAL@128..136 "r\"(())\"" [Newline("\n")] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@136..145 - 0: R_STRING_VALUE@136..145 - 0: R_RAW_STRING_LITERAL@136..145 "r\"( ())\"" [Newline("\n")] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@145..154 - 0: R_STRING_VALUE@145..154 - 0: R_RAW_STRING_LITERAL@145..154 "r\"(() )\"" [Newline("\n")] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@154..163 - 0: R_STRING_VALUE@154..163 - 0: R_RAW_STRING_LITERAL@154..163 "r\"-())-\"" [Newline("\n")] [] - 1: (empty) - 18: R_EXPRESSION_STATEMENT@163..175 - 0: R_STRING_VALUE@163..175 - 0: R_RAW_STRING_LITERAL@163..175 "r\"-(())-)-\"" [Newline("\n")] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@175..192 - 0: R_STRING_VALUE@175..192 - 0: R_RAW_STRING_LITERAL@175..192 "r\"(raw\nstring\n)\"" [Newline("\n")] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@192..224 - 0: R_STRING_VALUE@192..224 - 0: R_RAW_STRING_LITERAL@192..224 "r\"(\"some raw string\nbusiness\")\"" [Newline("\n")] [] - 1: (empty) - 21: R_EXPRESSION_STATEMENT@224..235 - 0: R_STRING_VALUE@224..235 - 0: R_RAW_STRING_LITERAL@224..235 "R\"(upper)\"" [Newline("\n")] [] - 1: (empty) - 22: R_EXPRESSION_STATEMENT@235..247 - 0: R_STRING_VALUE@235..247 - 0: R_RAW_STRING_LITERAL@235..247 "r'(single)'" [Newline("\n")] [] - 1: (empty) - 23: R_EXPRESSION_STATEMENT@247..265 - 0: R_STRING_VALUE@247..265 - 0: R_RAW_STRING_LITERAL@247..265 "R'(upper single)'" [Newline("\n")] [] - 1: (empty) - 24: R_EXPRESSION_STATEMENT@265..279 - 0: R_STRING_VALUE@265..279 - 0: R_RAW_STRING_LITERAL@265..279 "r\"[brackets]\"" [Newline("\n")] [] - 1: (empty) - 25: R_EXPRESSION_STATEMENT@279..291 - 0: R_STRING_VALUE@279..291 - 0: R_RAW_STRING_LITERAL@279..291 "r\"{braces}\"" [Newline("\n")] [] - 1: (empty) - 26: R_EXPRESSION_STATEMENT@291..294 - 0: R_STRING_VALUE@291..294 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@291..294 "\"\"" [Newline("\n")] [] - 1: (empty) - 27: R_EXPRESSION_STATEMENT@294..297 - 0: R_STRING_VALUE@294..297 - 0: R_SINGLE_QUOTED_STRING_LITERAL@294..297 "''" [Newline("\n")] [] - 1: (empty) - 28: R_EXPRESSION_STATEMENT@297..303 - 0: R_STRING_VALUE@297..303 - 0: R_RAW_STRING_LITERAL@297..303 "r\"()\"" [Newline("\n")] [] - 1: (empty) - 2: EOF@303..304 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/ok/while_statement.R b/crates/air_r_parser/tests/snapshots/ok/while_statement.R deleted file mode 100644 index badffae8..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/while_statement.R +++ /dev/null @@ -1,11 +0,0 @@ -while(TRUE) FALSE - -while(TRUE) {} - -while(a) { - -} - -while(fn()) { - -} diff --git a/crates/air_r_parser/tests/snapshots/ok/while_statement.R.snap b/crates/air_r_parser/tests/snapshots/ok/while_statement.R.snap deleted file mode 100644 index 7b25e202..00000000 --- a/crates/air_r_parser/tests/snapshots/ok/while_statement.R.snap +++ /dev/null @@ -1,162 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -while(TRUE) FALSE - -while(TRUE) {} - -while(a) { - -} - -while(fn()) { - -} - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RWhileStatement { - while_token: WHILE_KW@0..5 "while" [] [], - l_paren_token: L_PAREN@5..6 "(" [] [], - condition: RTrueExpression { - true_token: TRUE_KW@6..10 "TRUE" [] [], - }, - r_paren_token: R_PAREN@10..12 ")" [] [Whitespace(" ")], - body: RFalseExpression { - false_token: FALSE_KW@12..17 "FALSE" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RWhileStatement { - while_token: WHILE_KW@17..24 "while" [Newline("\n"), Newline("\n")] [], - l_paren_token: L_PAREN@24..25 "(" [] [], - condition: RTrueExpression { - true_token: TRUE_KW@25..29 "TRUE" [] [], - }, - r_paren_token: R_PAREN@29..31 ")" [] [Whitespace(" ")], - body: RBracedStatements { - l_curly_token: L_CURLY@31..32 "{" [] [], - statements: RStatementList [], - r_curly_token: R_CURLY@32..33 "}" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RWhileStatement { - while_token: WHILE_KW@33..40 "while" [Newline("\n"), Newline("\n")] [], - l_paren_token: L_PAREN@40..41 "(" [] [], - condition: RIdentifier { - name_token: IDENT@41..42 "a" [] [], - }, - r_paren_token: R_PAREN@42..44 ")" [] [Whitespace(" ")], - body: RBracedStatements { - l_curly_token: L_CURLY@44..45 "{" [] [], - statements: RStatementList [], - r_curly_token: R_CURLY@45..48 "}" [Newline("\n"), Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RWhileStatement { - while_token: WHILE_KW@48..55 "while" [Newline("\n"), Newline("\n")] [], - l_paren_token: L_PAREN@55..56 "(" [] [], - condition: RCall { - function: RIdentifier { - name_token: IDENT@56..58 "fn" [] [], - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@58..59 "(" [] [], - items: RArgumentList [], - r_paren_token: R_PAREN@59..60 ")" [] [], - }, - }, - r_paren_token: R_PAREN@60..62 ")" [] [Whitespace(" ")], - body: RBracedStatements { - l_curly_token: L_CURLY@62..63 "{" [] [], - statements: RStatementList [], - r_curly_token: R_CURLY@63..66 "}" [Newline("\n"), Newline("\n")] [], - }, - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@66..67 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..67 - 0: (empty) - 1: R_STATEMENT_LIST@0..66 - 0: R_EXPRESSION_STATEMENT@0..17 - 0: R_WHILE_STATEMENT@0..17 - 0: WHILE_KW@0..5 "while" [] [] - 1: L_PAREN@5..6 "(" [] [] - 2: R_TRUE_EXPRESSION@6..10 - 0: TRUE_KW@6..10 "TRUE" [] [] - 3: R_PAREN@10..12 ")" [] [Whitespace(" ")] - 4: R_FALSE_EXPRESSION@12..17 - 0: FALSE_KW@12..17 "FALSE" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@17..33 - 0: R_WHILE_STATEMENT@17..33 - 0: WHILE_KW@17..24 "while" [Newline("\n"), Newline("\n")] [] - 1: L_PAREN@24..25 "(" [] [] - 2: R_TRUE_EXPRESSION@25..29 - 0: TRUE_KW@25..29 "TRUE" [] [] - 3: R_PAREN@29..31 ")" [] [Whitespace(" ")] - 4: R_BRACED_STATEMENTS@31..33 - 0: L_CURLY@31..32 "{" [] [] - 1: R_STATEMENT_LIST@32..32 - 2: R_CURLY@32..33 "}" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@33..48 - 0: R_WHILE_STATEMENT@33..48 - 0: WHILE_KW@33..40 "while" [Newline("\n"), Newline("\n")] [] - 1: L_PAREN@40..41 "(" [] [] - 2: R_IDENTIFIER@41..42 - 0: IDENT@41..42 "a" [] [] - 3: R_PAREN@42..44 ")" [] [Whitespace(" ")] - 4: R_BRACED_STATEMENTS@44..48 - 0: L_CURLY@44..45 "{" [] [] - 1: R_STATEMENT_LIST@45..45 - 2: R_CURLY@45..48 "}" [Newline("\n"), Newline("\n")] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@48..66 - 0: R_WHILE_STATEMENT@48..66 - 0: WHILE_KW@48..55 "while" [Newline("\n"), Newline("\n")] [] - 1: L_PAREN@55..56 "(" [] [] - 2: R_CALL@56..60 - 0: R_IDENTIFIER@56..58 - 0: IDENT@56..58 "fn" [] [] - 1: R_CALL_ARGUMENTS@58..60 - 0: L_PAREN@58..59 "(" [] [] - 1: R_ARGUMENT_LIST@59..59 - 2: R_PAREN@59..60 ")" [] [] - 3: R_PAREN@60..62 ")" [] [Whitespace(" ")] - 4: R_BRACED_STATEMENTS@62..66 - 0: L_CURLY@62..63 "{" [] [] - 1: R_STATEMENT_LIST@63..63 - 2: R_CURLY@63..66 "}" [Newline("\n"), Newline("\n")] [] - 1: (empty) - 2: EOF@66..67 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/undefined/extract_expression_error.R b/crates/air_r_parser/tests/snapshots/undefined/extract_expression_error.R deleted file mode 100644 index 4a4ca3be..00000000 --- a/crates/air_r_parser/tests/snapshots/undefined/extract_expression_error.R +++ /dev/null @@ -1,29 +0,0 @@ -# TODO: tree-sitter-r currently allows an `optional()` RHS, but our grammar -# requires a RHS, so we get `missing (required)`. These should eventually be -# parse errors if we can switch tree-sitter-r away from using `optional()`. -# For example, `a$1` gives: - -#> RExtractExpression { -#> left: RDoubleValue { -#> value_token: R_DOUBLE_LITERAL@10..16 "1" [Newline("\n"), Whitespace(" ")] [], -#> }, -#> operator: DOLLAR@16..17 "$" [] [], -#> right: missing (required), -#> }, -#> RDoubleValue { -#> value_token: R_DOUBLE_LITERAL@17..18 "2" [] [], -#> }, - -a$1 -a$NA -a$NULL -a$TRUE -a$(b) -a${ b } - -a@1 -a@NA -a@NULL -a@TRUE -a@(b) -a@{ b } diff --git a/crates/air_r_parser/tests/snapshots/undefined/extract_expression_error.R.snap b/crates/air_r_parser/tests/snapshots/undefined/extract_expression_error.R.snap deleted file mode 100644 index 1f105c8a..00000000 --- a/crates/air_r_parser/tests/snapshots/undefined/extract_expression_error.R.snap +++ /dev/null @@ -1,440 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -# TODO: tree-sitter-r currently allows an `optional()` RHS, but our grammar -# requires a RHS, so we get `missing (required)`. These should eventually be -# parse errors if we can switch tree-sitter-r away from using `optional()`. -# For example, `a$1` gives: - -#> RExtractExpression { -#> left: RDoubleValue { -#> value_token: R_DOUBLE_LITERAL@10..16 "1" [Newline("\n"), Whitespace(" ")] [], -#> }, -#> operator: DOLLAR@16..17 "$" [] [], -#> right: missing (required), -#> }, -#> RDoubleValue { -#> value_token: R_DOUBLE_LITERAL@17..18 "2" [] [], -#> }, - -a$1 -a$NA -a$NULL -a$TRUE -a$(b) -a${ b } - -a@1 -a@NA -a@NULL -a@TRUE -a@(b) -a@{ b } - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@0..575 "a" [Comments("# TODO: tree-sitter-r ..."), Newline("\n"), Comments("# requires a RHS, so ..."), Newline("\n"), Comments("# parse errors if we ..."), Newline("\n"), Comments("# For example, `a$1` ..."), Newline("\n"), Newline("\n"), Comments("#> RExtractExpression {"), Newline("\n"), Comments("#> left: RDoubleV ..."), Newline("\n"), Comments("#> value_toke ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Comments("#> operator: DOLL ..."), Newline("\n"), Comments("#> right: missing ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Comments("#> RDoubleValue {"), Newline("\n"), Comments("#> value_token: R ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Newline("\n")] [], - }, - operator: DOLLAR@575..576 "$" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@576..577 "1" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@577..579 "a" [Newline("\n")] [], - }, - operator: DOLLAR@579..580 "$" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNaExpression { - value: NA_LOGICAL_KW@580..582 "NA" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@582..584 "a" [Newline("\n")] [], - }, - operator: DOLLAR@584..585 "$" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNullExpression { - null_token: NULL_KW@585..589 "NULL" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@589..591 "a" [Newline("\n")] [], - }, - operator: DOLLAR@591..592 "$" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RTrueExpression { - true_token: TRUE_KW@592..596 "TRUE" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RExtractExpression { - left: RIdentifier { - name_token: IDENT@596..598 "a" [Newline("\n")] [], - }, - operator: DOLLAR@598..599 "$" [] [], - right: missing (required), - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@599..600 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@600..601 "b" [] [], - }, - }, - ], - r_paren_token: R_PAREN@601..602 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@602..604 "a" [Newline("\n")] [], - }, - operator: DOLLAR@604..605 "$" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@605..607 "{" [] [Whitespace(" ")], - statements: RStatementList [ - RExpressionStatement { - expression: RIdentifier { - name_token: IDENT@607..609 "b" [] [Whitespace(" ")], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@609..610 "}" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@610..613 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: AT@613..614 "@" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@614..615 "1" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@615..617 "a" [Newline("\n")] [], - }, - operator: AT@617..618 "@" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNaExpression { - value: NA_LOGICAL_KW@618..620 "NA" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@620..622 "a" [Newline("\n")] [], - }, - operator: AT@622..623 "@" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNullExpression { - null_token: NULL_KW@623..627 "NULL" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@627..629 "a" [Newline("\n")] [], - }, - operator: AT@629..630 "@" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RTrueExpression { - true_token: TRUE_KW@630..634 "TRUE" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RExtractExpression { - left: RIdentifier { - name_token: IDENT@634..636 "a" [Newline("\n")] [], - }, - operator: AT@636..637 "@" [] [], - right: missing (required), - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@637..638 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@638..639 "b" [] [], - }, - }, - ], - r_paren_token: R_PAREN@639..640 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RExtractExpression { - left: RIdentifier { - name_token: IDENT@640..642 "a" [Newline("\n")] [], - }, - operator: AT@642..643 "@" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@643..645 "{" [] [Whitespace(" ")], - statements: RStatementList [ - RExpressionStatement { - expression: RIdentifier { - name_token: IDENT@645..647 "b" [] [Whitespace(" ")], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@647..648 "}" [] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@648..649 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..649 - 0: (empty) - 1: R_STATEMENT_LIST@0..648 - 0: R_EXPRESSION_STATEMENT@0..576 - 0: R_EXTRACT_EXPRESSION@0..576 - 0: R_IDENTIFIER@0..575 - 0: IDENT@0..575 "a" [Comments("# TODO: tree-sitter-r ..."), Newline("\n"), Comments("# requires a RHS, so ..."), Newline("\n"), Comments("# parse errors if we ..."), Newline("\n"), Comments("# For example, `a$1` ..."), Newline("\n"), Newline("\n"), Comments("#> RExtractExpression {"), Newline("\n"), Comments("#> left: RDoubleV ..."), Newline("\n"), Comments("#> value_toke ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Comments("#> operator: DOLL ..."), Newline("\n"), Comments("#> right: missing ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Comments("#> RDoubleValue {"), Newline("\n"), Comments("#> value_token: R ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Newline("\n")] [] - 1: DOLLAR@575..576 "$" [] [] - 2: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@576..577 - 0: R_DOUBLE_VALUE@576..577 - 0: R_DOUBLE_LITERAL@576..577 "1" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@577..580 - 0: R_EXTRACT_EXPRESSION@577..580 - 0: R_IDENTIFIER@577..579 - 0: IDENT@577..579 "a" [Newline("\n")] [] - 1: DOLLAR@579..580 "$" [] [] - 2: (empty) - 1: (empty) - 3: R_EXPRESSION_STATEMENT@580..582 - 0: R_NA_EXPRESSION@580..582 - 0: NA_LOGICAL_KW@580..582 "NA" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@582..585 - 0: R_EXTRACT_EXPRESSION@582..585 - 0: R_IDENTIFIER@582..584 - 0: IDENT@582..584 "a" [Newline("\n")] [] - 1: DOLLAR@584..585 "$" [] [] - 2: (empty) - 1: (empty) - 5: R_EXPRESSION_STATEMENT@585..589 - 0: R_NULL_EXPRESSION@585..589 - 0: NULL_KW@585..589 "NULL" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@589..592 - 0: R_EXTRACT_EXPRESSION@589..592 - 0: R_IDENTIFIER@589..591 - 0: IDENT@589..591 "a" [Newline("\n")] [] - 1: DOLLAR@591..592 "$" [] [] - 2: (empty) - 1: (empty) - 7: R_EXPRESSION_STATEMENT@592..596 - 0: R_TRUE_EXPRESSION@592..596 - 0: TRUE_KW@592..596 "TRUE" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@596..602 - 0: R_CALL@596..602 - 0: R_EXTRACT_EXPRESSION@596..599 - 0: R_IDENTIFIER@596..598 - 0: IDENT@596..598 "a" [Newline("\n")] [] - 1: DOLLAR@598..599 "$" [] [] - 2: (empty) - 1: R_CALL_ARGUMENTS@599..602 - 0: L_PAREN@599..600 "(" [] [] - 1: R_ARGUMENT_LIST@600..601 - 0: R_ARGUMENT@600..601 - 0: (empty) - 1: R_IDENTIFIER@600..601 - 0: IDENT@600..601 "b" [] [] - 2: R_PAREN@601..602 ")" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@602..605 - 0: R_EXTRACT_EXPRESSION@602..605 - 0: R_IDENTIFIER@602..604 - 0: IDENT@602..604 "a" [Newline("\n")] [] - 1: DOLLAR@604..605 "$" [] [] - 2: (empty) - 1: (empty) - 10: R_EXPRESSION_STATEMENT@605..610 - 0: R_BRACED_STATEMENTS@605..610 - 0: L_CURLY@605..607 "{" [] [Whitespace(" ")] - 1: R_STATEMENT_LIST@607..609 - 0: R_EXPRESSION_STATEMENT@607..609 - 0: R_IDENTIFIER@607..609 - 0: IDENT@607..609 "b" [] [Whitespace(" ")] - 1: (empty) - 2: R_CURLY@609..610 "}" [] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@610..614 - 0: R_EXTRACT_EXPRESSION@610..614 - 0: R_IDENTIFIER@610..613 - 0: IDENT@610..613 "a" [Newline("\n"), Newline("\n")] [] - 1: AT@613..614 "@" [] [] - 2: (empty) - 1: (empty) - 12: R_EXPRESSION_STATEMENT@614..615 - 0: R_DOUBLE_VALUE@614..615 - 0: R_DOUBLE_LITERAL@614..615 "1" [] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@615..618 - 0: R_EXTRACT_EXPRESSION@615..618 - 0: R_IDENTIFIER@615..617 - 0: IDENT@615..617 "a" [Newline("\n")] [] - 1: AT@617..618 "@" [] [] - 2: (empty) - 1: (empty) - 14: R_EXPRESSION_STATEMENT@618..620 - 0: R_NA_EXPRESSION@618..620 - 0: NA_LOGICAL_KW@618..620 "NA" [] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@620..623 - 0: R_EXTRACT_EXPRESSION@620..623 - 0: R_IDENTIFIER@620..622 - 0: IDENT@620..622 "a" [Newline("\n")] [] - 1: AT@622..623 "@" [] [] - 2: (empty) - 1: (empty) - 16: R_EXPRESSION_STATEMENT@623..627 - 0: R_NULL_EXPRESSION@623..627 - 0: NULL_KW@623..627 "NULL" [] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@627..630 - 0: R_EXTRACT_EXPRESSION@627..630 - 0: R_IDENTIFIER@627..629 - 0: IDENT@627..629 "a" [Newline("\n")] [] - 1: AT@629..630 "@" [] [] - 2: (empty) - 1: (empty) - 18: R_EXPRESSION_STATEMENT@630..634 - 0: R_TRUE_EXPRESSION@630..634 - 0: TRUE_KW@630..634 "TRUE" [] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@634..640 - 0: R_CALL@634..640 - 0: R_EXTRACT_EXPRESSION@634..637 - 0: R_IDENTIFIER@634..636 - 0: IDENT@634..636 "a" [Newline("\n")] [] - 1: AT@636..637 "@" [] [] - 2: (empty) - 1: R_CALL_ARGUMENTS@637..640 - 0: L_PAREN@637..638 "(" [] [] - 1: R_ARGUMENT_LIST@638..639 - 0: R_ARGUMENT@638..639 - 0: (empty) - 1: R_IDENTIFIER@638..639 - 0: IDENT@638..639 "b" [] [] - 2: R_PAREN@639..640 ")" [] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@640..643 - 0: R_EXTRACT_EXPRESSION@640..643 - 0: R_IDENTIFIER@640..642 - 0: IDENT@640..642 "a" [Newline("\n")] [] - 1: AT@642..643 "@" [] [] - 2: (empty) - 1: (empty) - 21: R_EXPRESSION_STATEMENT@643..648 - 0: R_BRACED_STATEMENTS@643..648 - 0: L_CURLY@643..645 "{" [] [Whitespace(" ")] - 1: R_STATEMENT_LIST@645..647 - 0: R_EXPRESSION_STATEMENT@645..647 - 0: R_IDENTIFIER@645..647 - 0: IDENT@645..647 "b" [] [Whitespace(" ")] - 1: (empty) - 2: R_CURLY@647..648 "}" [] [] - 1: (empty) - 2: EOF@648..649 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/snapshots/undefined/namespace_expression_error.R b/crates/air_r_parser/tests/snapshots/undefined/namespace_expression_error.R deleted file mode 100644 index 6cd3b8ba..00000000 --- a/crates/air_r_parser/tests/snapshots/undefined/namespace_expression_error.R +++ /dev/null @@ -1,29 +0,0 @@ -# TODO: tree-sitter-r currently allows an `optional()` RHS, but our grammar -# requires a RHS, so we get `missing (required)`. These should eventually be -# parse errors if we can switch tree-sitter-r away from using `optional()`. -# For example, `a::1` gives: - -#> RNamespaceExpression { -#> left: RIdentifier { -#> name_token: IDENT@0..6 "a" [Newline("\n"), Whitespace(" ")] [], -#> }, -#> operator: COLON2@6..8 "::" [] [], -#> right: missing (required), -#> }, -#> RDoubleValue { -#> value_token: R_DOUBLE_LITERAL@8..9 "1" [] [], -#> }, - -a::1 -a::NA -a::NULL -a::TRUE -a::(b) -a::{ b } - -a:::1 -a:::NA -a:::NULL -a:::TRUE -a:::(b) -a:::{ b } diff --git a/crates/air_r_parser/tests/snapshots/undefined/namespace_expression_error.R.snap b/crates/air_r_parser/tests/snapshots/undefined/namespace_expression_error.R.snap deleted file mode 100644 index 37ea9316..00000000 --- a/crates/air_r_parser/tests/snapshots/undefined/namespace_expression_error.R.snap +++ /dev/null @@ -1,440 +0,0 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input - -```R -# TODO: tree-sitter-r currently allows an `optional()` RHS, but our grammar -# requires a RHS, so we get `missing (required)`. These should eventually be -# parse errors if we can switch tree-sitter-r away from using `optional()`. -# For example, `a::1` gives: - -#> RNamespaceExpression { -#> left: RIdentifier { -#> name_token: IDENT@0..6 "a" [Newline("\n"), Whitespace(" ")] [], -#> }, -#> operator: COLON2@6..8 "::" [] [], -#> right: missing (required), -#> }, -#> RDoubleValue { -#> value_token: R_DOUBLE_LITERAL@8..9 "1" [] [], -#> }, - -a::1 -a::NA -a::NULL -a::TRUE -a::(b) -a::{ b } - -a:::1 -a:::NA -a:::NULL -a:::TRUE -a:::(b) -a:::{ b } - -``` - - -## AST - -``` -RRoot { - bom_token: missing (optional), - statements: RStatementList [ - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@0..560 "a" [Comments("# TODO: tree-sitter-r ..."), Newline("\n"), Comments("# requires a RHS, so ..."), Newline("\n"), Comments("# parse errors if we ..."), Newline("\n"), Comments("# For example, `a::1` ..."), Newline("\n"), Newline("\n"), Comments("#> RNamespaceExpressi ..."), Newline("\n"), Comments("#> left: RIdentif ..."), Newline("\n"), Comments("#> name_token ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Comments("#> operator: COLO ..."), Newline("\n"), Comments("#> right: missing ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Comments("#> RDoubleValue {"), Newline("\n"), Comments("#> value_token: R ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Newline("\n")] [], - }, - operator: COLON2@560..562 "::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@562..563 "1" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@563..565 "a" [Newline("\n")] [], - }, - operator: COLON2@565..567 "::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNaExpression { - value: NA_LOGICAL_KW@567..569 "NA" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@569..571 "a" [Newline("\n")] [], - }, - operator: COLON2@571..573 "::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNullExpression { - null_token: NULL_KW@573..577 "NULL" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@577..579 "a" [Newline("\n")] [], - }, - operator: COLON2@579..581 "::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RTrueExpression { - true_token: TRUE_KW@581..585 "TRUE" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@585..587 "a" [Newline("\n")] [], - }, - operator: COLON2@587..589 "::" [] [], - right: missing (required), - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@589..590 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@590..591 "b" [] [], - }, - }, - ], - r_paren_token: R_PAREN@591..592 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@592..594 "a" [Newline("\n")] [], - }, - operator: COLON2@594..596 "::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@596..598 "{" [] [Whitespace(" ")], - statements: RStatementList [ - RExpressionStatement { - expression: RIdentifier { - name_token: IDENT@598..600 "b" [] [Whitespace(" ")], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@600..601 "}" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@601..604 "a" [Newline("\n"), Newline("\n")] [], - }, - operator: COLON3@604..607 ":::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RDoubleValue { - value_token: R_DOUBLE_LITERAL@607..608 "1" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@608..610 "a" [Newline("\n")] [], - }, - operator: COLON3@610..613 ":::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNaExpression { - value: NA_LOGICAL_KW@613..615 "NA" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@615..617 "a" [Newline("\n")] [], - }, - operator: COLON3@617..620 ":::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNullExpression { - null_token: NULL_KW@620..624 "NULL" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@624..626 "a" [Newline("\n")] [], - }, - operator: COLON3@626..629 ":::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RTrueExpression { - true_token: TRUE_KW@629..633 "TRUE" [] [], - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RCall { - function: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@633..635 "a" [Newline("\n")] [], - }, - operator: COLON3@635..638 ":::" [] [], - right: missing (required), - }, - arguments: RCallArguments { - l_paren_token: L_PAREN@638..639 "(" [] [], - items: RArgumentList [ - RArgument { - name_clause: missing (optional), - value: RIdentifier { - name_token: IDENT@639..640 "b" [] [], - }, - }, - ], - r_paren_token: R_PAREN@640..641 ")" [] [], - }, - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RNamespaceExpression { - left: RIdentifier { - name_token: IDENT@641..643 "a" [Newline("\n")] [], - }, - operator: COLON3@643..646 ":::" [] [], - right: missing (required), - }, - semicolon_token: missing (optional), - }, - RExpressionStatement { - expression: RBracedStatements { - l_curly_token: L_CURLY@646..648 "{" [] [Whitespace(" ")], - statements: RStatementList [ - RExpressionStatement { - expression: RIdentifier { - name_token: IDENT@648..650 "b" [] [Whitespace(" ")], - }, - semicolon_token: missing (optional), - }, - ], - r_curly_token: R_CURLY@650..651 "}" [] [], - }, - semicolon_token: missing (optional), - }, - ], - eof_token: EOF@651..652 "" [Newline("\n")] [], -} -``` - -## CST - -``` -0: R_ROOT@0..652 - 0: (empty) - 1: R_STATEMENT_LIST@0..651 - 0: R_EXPRESSION_STATEMENT@0..562 - 0: R_NAMESPACE_EXPRESSION@0..562 - 0: R_IDENTIFIER@0..560 - 0: IDENT@0..560 "a" [Comments("# TODO: tree-sitter-r ..."), Newline("\n"), Comments("# requires a RHS, so ..."), Newline("\n"), Comments("# parse errors if we ..."), Newline("\n"), Comments("# For example, `a::1` ..."), Newline("\n"), Newline("\n"), Comments("#> RNamespaceExpressi ..."), Newline("\n"), Comments("#> left: RIdentif ..."), Newline("\n"), Comments("#> name_token ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Comments("#> operator: COLO ..."), Newline("\n"), Comments("#> right: missing ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Comments("#> RDoubleValue {"), Newline("\n"), Comments("#> value_token: R ..."), Newline("\n"), Comments("#> },"), Newline("\n"), Newline("\n")] [] - 1: COLON2@560..562 "::" [] [] - 2: (empty) - 1: (empty) - 1: R_EXPRESSION_STATEMENT@562..563 - 0: R_DOUBLE_VALUE@562..563 - 0: R_DOUBLE_LITERAL@562..563 "1" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@563..567 - 0: R_NAMESPACE_EXPRESSION@563..567 - 0: R_IDENTIFIER@563..565 - 0: IDENT@563..565 "a" [Newline("\n")] [] - 1: COLON2@565..567 "::" [] [] - 2: (empty) - 1: (empty) - 3: R_EXPRESSION_STATEMENT@567..569 - 0: R_NA_EXPRESSION@567..569 - 0: NA_LOGICAL_KW@567..569 "NA" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@569..573 - 0: R_NAMESPACE_EXPRESSION@569..573 - 0: R_IDENTIFIER@569..571 - 0: IDENT@569..571 "a" [Newline("\n")] [] - 1: COLON2@571..573 "::" [] [] - 2: (empty) - 1: (empty) - 5: R_EXPRESSION_STATEMENT@573..577 - 0: R_NULL_EXPRESSION@573..577 - 0: NULL_KW@573..577 "NULL" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@577..581 - 0: R_NAMESPACE_EXPRESSION@577..581 - 0: R_IDENTIFIER@577..579 - 0: IDENT@577..579 "a" [Newline("\n")] [] - 1: COLON2@579..581 "::" [] [] - 2: (empty) - 1: (empty) - 7: R_EXPRESSION_STATEMENT@581..585 - 0: R_TRUE_EXPRESSION@581..585 - 0: TRUE_KW@581..585 "TRUE" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@585..592 - 0: R_CALL@585..592 - 0: R_NAMESPACE_EXPRESSION@585..589 - 0: R_IDENTIFIER@585..587 - 0: IDENT@585..587 "a" [Newline("\n")] [] - 1: COLON2@587..589 "::" [] [] - 2: (empty) - 1: R_CALL_ARGUMENTS@589..592 - 0: L_PAREN@589..590 "(" [] [] - 1: R_ARGUMENT_LIST@590..591 - 0: R_ARGUMENT@590..591 - 0: (empty) - 1: R_IDENTIFIER@590..591 - 0: IDENT@590..591 "b" [] [] - 2: R_PAREN@591..592 ")" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@592..596 - 0: R_NAMESPACE_EXPRESSION@592..596 - 0: R_IDENTIFIER@592..594 - 0: IDENT@592..594 "a" [Newline("\n")] [] - 1: COLON2@594..596 "::" [] [] - 2: (empty) - 1: (empty) - 10: R_EXPRESSION_STATEMENT@596..601 - 0: R_BRACED_STATEMENTS@596..601 - 0: L_CURLY@596..598 "{" [] [Whitespace(" ")] - 1: R_STATEMENT_LIST@598..600 - 0: R_EXPRESSION_STATEMENT@598..600 - 0: R_IDENTIFIER@598..600 - 0: IDENT@598..600 "b" [] [Whitespace(" ")] - 1: (empty) - 2: R_CURLY@600..601 "}" [] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@601..607 - 0: R_NAMESPACE_EXPRESSION@601..607 - 0: R_IDENTIFIER@601..604 - 0: IDENT@601..604 "a" [Newline("\n"), Newline("\n")] [] - 1: COLON3@604..607 ":::" [] [] - 2: (empty) - 1: (empty) - 12: R_EXPRESSION_STATEMENT@607..608 - 0: R_DOUBLE_VALUE@607..608 - 0: R_DOUBLE_LITERAL@607..608 "1" [] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@608..613 - 0: R_NAMESPACE_EXPRESSION@608..613 - 0: R_IDENTIFIER@608..610 - 0: IDENT@608..610 "a" [Newline("\n")] [] - 1: COLON3@610..613 ":::" [] [] - 2: (empty) - 1: (empty) - 14: R_EXPRESSION_STATEMENT@613..615 - 0: R_NA_EXPRESSION@613..615 - 0: NA_LOGICAL_KW@613..615 "NA" [] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@615..620 - 0: R_NAMESPACE_EXPRESSION@615..620 - 0: R_IDENTIFIER@615..617 - 0: IDENT@615..617 "a" [Newline("\n")] [] - 1: COLON3@617..620 ":::" [] [] - 2: (empty) - 1: (empty) - 16: R_EXPRESSION_STATEMENT@620..624 - 0: R_NULL_EXPRESSION@620..624 - 0: NULL_KW@620..624 "NULL" [] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@624..629 - 0: R_NAMESPACE_EXPRESSION@624..629 - 0: R_IDENTIFIER@624..626 - 0: IDENT@624..626 "a" [Newline("\n")] [] - 1: COLON3@626..629 ":::" [] [] - 2: (empty) - 1: (empty) - 18: R_EXPRESSION_STATEMENT@629..633 - 0: R_TRUE_EXPRESSION@629..633 - 0: TRUE_KW@629..633 "TRUE" [] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@633..641 - 0: R_CALL@633..641 - 0: R_NAMESPACE_EXPRESSION@633..638 - 0: R_IDENTIFIER@633..635 - 0: IDENT@633..635 "a" [Newline("\n")] [] - 1: COLON3@635..638 ":::" [] [] - 2: (empty) - 1: R_CALL_ARGUMENTS@638..641 - 0: L_PAREN@638..639 "(" [] [] - 1: R_ARGUMENT_LIST@639..640 - 0: R_ARGUMENT@639..640 - 0: (empty) - 1: R_IDENTIFIER@639..640 - 0: IDENT@639..640 "b" [] [] - 2: R_PAREN@640..641 ")" [] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@641..646 - 0: R_NAMESPACE_EXPRESSION@641..646 - 0: R_IDENTIFIER@641..643 - 0: IDENT@641..643 "a" [Newline("\n")] [] - 1: COLON3@643..646 ":::" [] [] - 2: (empty) - 1: (empty) - 21: R_EXPRESSION_STATEMENT@646..651 - 0: R_BRACED_STATEMENTS@646..651 - 0: L_CURLY@646..648 "{" [] [Whitespace(" ")] - 1: R_STATEMENT_LIST@648..650 - 0: R_EXPRESSION_STATEMENT@648..650 - 0: R_IDENTIFIER@648..650 - 0: IDENT@648..650 "b" [] [Whitespace(" ")] - 1: (empty) - 2: R_CURLY@650..651 "}" [] [] - 1: (empty) - 2: EOF@651..652 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser/tests/spec_test.rs b/crates/air_r_parser/tests/spec_test.rs deleted file mode 100644 index 39660490..00000000 --- a/crates/air_r_parser/tests/spec_test.rs +++ /dev/null @@ -1,141 +0,0 @@ -use air_r_parser::{RParserOptions, parse}; -use air_r_syntax::RSyntaxNode; -use biome_rowan::SyntaxKind; -use biome_rowan::SyntaxNode; -use biome_rowan::SyntaxSlot; -use std::fmt::Write; -use std::fs; -use std::path::Path; - -#[derive(Copy, Clone)] -pub enum ExpectedOutcome { - Pass, - Fail, - Undefined, -} - -#[ignore] -#[test] -fn quick_test() { - let code = " - 1$foo - 1$2"; - let options = RParserOptions::default(); - let parsed = parse(code, options); - let root = parsed.tree(); - println!("{root:#?}"); -} - -pub fn run(test_case: &str, _snapshot_name: &str, test_directory: &str, outcome_str: &str) { - let outcome = match outcome_str { - "ok" => ExpectedOutcome::Pass, - "error" => ExpectedOutcome::Fail, - "undefined" => ExpectedOutcome::Undefined, - _ => panic!("Invalid expected outcome {outcome_str}"), - }; - - let test_case_path = Path::new(test_case); - - let file_name = test_case_path - .file_name() - .expect("Expected test to have a file name") - .to_str() - .expect("File name to be valid UTF8"); - - let content = fs::read_to_string(test_case_path) - .expect("Expected test path to be a readable file in UTF8 encoding"); - - let options = RParserOptions::default(); - let parsed = parse(&content, options); - let root = parsed.tree(); - let formatted_ast = format!("{:#?}", root); - - let mut snapshot = String::new(); - writeln!(snapshot, "\n## Input\n\n```R\n{content}\n```\n\n").unwrap(); - - writeln!( - snapshot, - r#"## AST - -``` -{formatted_ast} -``` - -## CST - -``` -{:#?} -``` -"#, - parsed.syntax() - ) - .unwrap(); - - let diagnostics = parsed.diagnostics(); - - if !diagnostics.is_empty() { - if matches!(outcome, ExpectedOutcome::Pass) { - panic!( - "Expected no errors to be present in a test case that is expected to pass but the following errors are present:\n{diagnostics:#?}" - ) - } - writeln!(snapshot, "## Errors\n\n```").unwrap(); - for diagnostic in diagnostics { - writeln!(snapshot, "{}", diagnostic.message).unwrap(); - } - writeln!(snapshot, "```\n").unwrap(); - } - - match outcome { - ExpectedOutcome::Pass => { - let missing_required = formatted_ast.contains("missing (required)"); - if missing_required - || parsed - .syntax() - .descendants() - .any(|node: RSyntaxNode| node.kind().is_bogus()) - { - panic!( - "Parsed tree of a 'OK' test case should not contain any missing required children or bogus nodes: \n {formatted_ast:#?} \n\n {formatted_ast}" - ); - } - - let syntax = parsed.syntax(); - if has_bogus_nodes_or_empty_slots(&syntax) { - panic!("modified tree has bogus nodes or empty slots:\n{syntax:#?} \n\n {syntax}") - } - } - ExpectedOutcome::Fail => { - if !parsed.has_errors() { - panic!("Failing test must have an error"); - } - } - _ => {} - } - - insta::with_settings!({ - prepend_module_to_snapshot => false, - snapshot_path => &test_directory, - }, { - insta::assert_snapshot!(file_name, snapshot); - }); -} - -/// This check is used in the parser test to ensure it doesn't emit -/// bogus nodes without diagnostics. -pub fn has_bogus_nodes_or_empty_slots(node: &SyntaxNode) -> bool { - node.descendants().any(|descendant| { - let kind = descendant.kind(); - if kind.is_bogus() { - return true; - } - - if kind.is_list() { - return descendant - .slots() - .any(|slot| matches!(slot, SyntaxSlot::Empty { .. })); - } - - false - }) -} diff --git a/crates/air_r_parser/tests/spec_tests.rs b/crates/air_r_parser/tests/spec_tests.rs deleted file mode 100644 index 37eeb04e..00000000 --- a/crates/air_r_parser/tests/spec_tests.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod spec_test; - -mod tests { - tests_macros::gen_tests! {"tests/snapshots/ok/**/*.R", crate::spec_test::run, "ok"} - - tests_macros::gen_tests! {"tests/snapshots/error/**/*.R", crate::spec_test::run, "error"} - - tests_macros::gen_tests! {"tests/snapshots/undefined/**/*.R", crate::spec_test::run, "undefined"} -} diff --git a/crates/air_r_parser_tests/Cargo.toml b/crates/air_r_parser_tests/Cargo.toml new file mode 100644 index 00000000..fc24e40c --- /dev/null +++ b/crates/air_r_parser_tests/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "air_r_parser_tests" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "The markdown tests and the harness that runs them" +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true +publish = false + +[dependencies] +air_r_parser = { workspace = true } +air_r_syntax = { workspace = true } +biome_console = { workspace = true } +biome_diagnostics = { workspace = true } +biome_parser = { workspace = true } +biome_rowan = { workspace = true } +similar = { workspace = true } + +[dev-dependencies] +air_r_test_macros = { workspace = true } + +[lints] +workspace = true diff --git a/crates/air_r_parser_tests/build.rs b/crates/air_r_parser_tests/build.rs new file mode 100644 index 00000000..06517ce9 --- /dev/null +++ b/crates/air_r_parser_tests/build.rs @@ -0,0 +1,4 @@ +fn main() { + // Rebuild when a new test file is added + println!("cargo::rerun-if-changed=tests/corpus"); +} diff --git a/crates/air_r_parser_tests/src/check.rs b/crates/air_r_parser_tests/src/check.rs new file mode 100644 index 00000000..f7df5fd5 --- /dev/null +++ b/crates/air_r_parser_tests/src/check.rs @@ -0,0 +1,187 @@ +use crate::document::Block; +use crate::document::Cell; +use crate::document::Expectation; +use air_r_parser::RParse; +use air_r_parser::RParserOptions; +use air_r_parser::parse; +use air_r_syntax::RSyntaxNode; +use air_r_syntax::TextRange; +use biome_console::fmt::Formatter; +use biome_console::fmt::Termcolor; +use biome_console::markup; +use biome_diagnostics::Diagnostic; +use biome_diagnostics::DiagnosticExt; +use biome_diagnostics::PrintDiagnostic; +use biome_diagnostics::termcolor::NoColor; +use biome_parser::diagnostic::ParseDiagnostic; +use biome_rowan::SyntaxKind; +use biome_rowan::SyntaxNode; + +#[derive(Debug)] +pub enum CheckError { + RoundTrip { expected: String, got: String }, + ExpectedError, + UnexpectedError(String), + BogusNodes(String), + UndiagnosedBogusNode { ranges: Vec, ast: String }, +} + +/// One code block to check +/// +/// Emits CST and diagnostic cells to write in the snapshot +pub fn check(block: &Block, expectation: Expectation) -> Result, CheckError> { + let parsed = parse(&block.source, RParserOptions::default()); + let ast = format!("{:#?}", parsed.tree()); + let root = parsed.syntax(); + + let round_tripped = root.to_string(); + if round_tripped != block.source { + return Err(CheckError::RoundTrip { + expected: block.source.clone(), + got: round_tripped, + }); + } + + let has_errors = parsed.has_errors(); + let has_bogus = has_bogus_nodes_or_missing_required_children(&ast, &root); + + match expectation { + Expectation::Error => { + if !has_errors { + return Err(CheckError::ExpectedError); + } + } + Expectation::Clean => { + if has_errors { + return Err(CheckError::UnexpectedError(render_diagnostics( + &parsed, + &block.source, + ))); + } + + if has_bogus { + return Err(CheckError::BogusNodes(ast)); + } + } + } + + let unexplained = undiagnosed_bogus_nodes(&root, parsed.diagnostics()); + if !unexplained.is_empty() { + return Err(CheckError::UndiagnosedBogusNode { + ranges: unexplained + .iter() + .map(SyntaxNode::text_trimmed_range) + .collect(), + ast, + }); + } + + let mut cells = vec![Cell::Cst(ast)]; + if !parsed.diagnostics().is_empty() { + cells.push(Cell::Diagnostics(render_diagnostics( + &parsed, + &block.source, + ))); + } + + Ok(cells) +} + +fn undiagnosed_bogus_nodes( + root: &RSyntaxNode, + diagnostics: &[ParseDiagnostic], +) -> Vec { + let spans: Vec = diagnostics + .iter() + .filter_map(|diagnostic| diagnostic.location().span) + .collect(); + + root.descendants() + .filter(|node| node.kind().is_bogus()) + .filter(|node| { + let range = node.text_trimmed_range(); + !spans.iter().any(|span| span.intersect(range).is_some()) + }) + .collect() +} + +fn has_bogus_nodes_or_missing_required_children(ast: &str, root: &RSyntaxNode) -> bool { + ast.contains("missing (required)") || root.descendants().any(|node| node.kind().is_bogus()) +} + +fn render_diagnostics(parse: &RParse, source: &str) -> String { + let mut out = Vec::new(); + + for diagnostic in parse.diagnostics() { + let diagnostic = diagnostic + .clone() + .with_file_path("test.R") + .with_file_source_code(source); + + Formatter::new(&mut Termcolor(NoColor::new(&mut out))) + .write_markup(markup! { {PrintDiagnostic::verbose(&diagnostic)} }) + .expect("writing a diagnostic to a string cannot fail"); + } + + let rendered = String::from_utf8(out).expect("rendered diagnostics are UTF-8"); + + // Biome pads its frames out to the terminal width. Committed snapshots have + // to survive an editor that strips trailing whitespace. + let mut trimmed = String::with_capacity(rendered.len()); + for line in rendered.lines() { + trimmed.push_str(line.trim_end()); + trimmed.push('\n'); + } + + trimmed.truncate(trimmed.trim_end().len()); + trimmed.push('\n'); + trimmed +} + +#[cfg(test)] +mod tests { + use super::*; + + const CLEAN: &str = "# a comment\n"; + + /// Causes a lexer diagnostic + const ERRORS: &str = "`a\n"; + + fn block(source: &str) -> Block { + Block { + source: source.to_string(), + generated: Vec::new(), + } + } + + #[test] + fn a_clean_block_gets_a_cst_cell_and_no_diagnostics_cell() { + let cells = check(&block(CLEAN), Expectation::Clean).expect("parses cleanly"); + + assert!(matches!(cells.as_slice(), [Cell::Cst(_)])); + } + + #[test] + fn an_error_block_gets_both_cells() { + let cells = check(&block(ERRORS), Expectation::Error).expect("errors as marked"); + + let [Cell::Cst(_), Cell::Diagnostics(diagnostics)] = cells.as_slice() else { + panic!("expected a cst cell and a diagnostics cell, got {cells:?}"); + }; + assert!(diagnostics.contains("Unterminated backtick-quoted name.")); + } + + #[test] + fn an_unmarked_block_may_not_error() { + let error = check(&block(ERRORS), Expectation::Clean).expect_err("errors"); + + assert!(matches!(error, CheckError::UnexpectedError(_))); + } + + #[test] + fn an_error_block_must_error() { + let error = check(&block(CLEAN), Expectation::Error).expect_err("parses cleanly"); + + assert!(matches!(error, CheckError::ExpectedError)); + } +} diff --git a/crates/air_r_parser_tests/src/corpus.rs b/crates/air_r_parser_tests/src/corpus.rs new file mode 100644 index 00000000..930e6c1d --- /dev/null +++ b/crates/air_r_parser_tests/src/corpus.rs @@ -0,0 +1,74 @@ +use std::path::Path; +use std::path::PathBuf; + +pub fn dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus") +} + +/// Every human-authored `*.md`, sorted +pub fn input_files() -> Vec { + files() + .into_iter() + .filter(|path| !is_snapshot(path)) + .collect() +} + +/// Every generated `*.snap.md`, sorted +pub fn snapshot_files() -> Vec { + files() + .into_iter() + .filter(|path| is_snapshot(path)) + .collect() +} + +/// The snapshot beside an input file +pub fn snapshot_of(input: &Path) -> PathBuf { + input.with_extension("snap.md") +} + +/// The path from the corpus root, which is what a snapshot banner records and +/// what a failure message should show +pub fn relative(path: &Path) -> String { + let mut parts: Vec = path + .components() + .rev() + .take_while(|component| component.as_os_str() != "corpus") + .map(|component| component.as_os_str().to_string_lossy().into_owned()) + .collect(); + + parts.reverse(); + parts.join("/") +} + +fn is_snapshot(path: &Path) -> bool { + name(path).ends_with(".snap.md") +} + +fn name(path: &Path) -> &str { + path.file_name() + .and_then(|name| name.to_str()) + .unwrap_or("") +} + +fn files() -> Vec { + let mut files = Vec::new(); + let mut directories = vec![dir()]; + + while let Some(directory) = directories.pop() { + let entries = std::fs::read_dir(&directory) + .unwrap_or_else(|err| panic!("cannot read {}: {err}", directory.display())); + + for entry in entries { + let path = entry.expect("cannot read directory entry").path(); + + if path.is_dir() { + directories.push(path); + } else if name(&path).ends_with(".md") { + files.push(path); + } + } + } + + files.sort(); + files +} diff --git a/crates/air_r_parser_tests/src/document.rs b/crates/air_r_parser_tests/src/document.rs new file mode 100644 index 00000000..7725e42b --- /dev/null +++ b/crates/air_r_parser_tests/src/document.rs @@ -0,0 +1,540 @@ +use std::fmt::Write as _; + +/// One corpus test file +/// +/// Covers both human created input files and generated snapshot files. The +/// only difference is that snapshot files have non empty `generated` cells. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Document { + pub sections: Vec
, +} + +/// One `##` heading and everything under it +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Section { + pub name: String, + pub expectation: Expectation, + + pub elements: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Element { + /// Copied verbatim into the snapshot, never parsed + Prose(String), + Block(Block), +} + +/// One R code cell and the CST and diagnostic cells generated from it +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Block { + pub source: String, + pub generated: Vec, +} + +/// A cell the harness produces, in snapshot files only +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Cell { + /// The CST view + Cst(String), + /// Rendered diagnostics + Diagnostics(String), +} + +/// What a heading's blocks must do when parsed +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum Expectation { + /// Every block must parse with zero diagnostics and no bogus nodes + #[default] + Clean, + /// Every block must produce at least one error diagnostic + Error, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DocumentError { + pub line: usize, + pub message: String, +} + +impl std::fmt::Display for DocumentError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "line {}: {}", self.line, self.message) + } +} + +impl std::error::Error for DocumentError {} + +impl Expectation { + fn parse(text: &str, line: usize) -> Result { + match text.trim() { + "error" => Ok(Expectation::Error), + other => Err(DocumentError { + line, + message: format!("unknown marker `{other}`, expected `error`"), + }), + } + } + + fn render(self) -> String { + match self { + Expectation::Clean => String::new(), + Expectation::Error => " [error]".to_string(), + } + } +} + +impl Section { + pub fn blocks(&self) -> impl DoubleEndedIterator { + self.elements.iter().filter_map(|element| match element { + Element::Block(block) => Some(block), + Element::Prose(_) => None, + }) + } + + pub fn blocks_mut(&mut self) -> impl DoubleEndedIterator { + self.elements + .iter_mut() + .filter_map(|element| match element { + Element::Block(block) => Some(block), + Element::Prose(_) => None, + }) + } +} + +impl Document { + pub fn parse(text: &str) -> Result { + Parser::new(text).parse() + } + + pub fn render_snapshot(&self) -> String { + let mut out = String::new(); + writeln!(out, "").unwrap(); + + for section in &self.sections { + writeln!(out, "\n## {}{}", section.name, section.expectation.render()).unwrap(); + + for element in §ion.elements { + match element { + Element::Prose(prose) => writeln!(out, "\n{prose}").unwrap(), + Element::Block(block) => render_block(&mut out, block), + } + } + } + + out + } + + /// A comparable shape for this section, to ensure nothing is dropped when + /// writing the snapshot file + pub fn shape(&self) -> Vec<(&str, Expectation, usize)> { + self.sections + .iter() + .map(|section| { + ( + section.name.as_str(), + section.expectation, + section.blocks().count(), + ) + }) + .collect() + } +} + +fn render_block(out: &mut String, block: &Block) { + render_fence(out, "r", &block.source); + + for cell in &block.generated { + match cell { + Cell::Cst(content) => render_fence(out, "cst", content), + Cell::Diagnostics(content) => render_fence(out, "diagnostics", content), + } + } +} + +fn render_fence(out: &mut String, language: &str, content: &str) { + out.push('\n'); + out.push_str("```"); + out.push_str(language); + out.push('\n'); + out.push_str(content); + // A cell whose content does not end its line would swallow the fence + if !content.is_empty() && !content.ends_with('\n') { + out.push('\n'); + } + out.push_str("```\n"); +} + +struct Parser<'src> { + lines: Vec<&'src str>, + index: usize, + sections: Vec
, + prose: Vec<&'src str>, +} + +impl<'src> Parser<'src> { + fn new(text: &'src str) -> Self { + // `split_inclusive` keeps each line's terminator, so a file that does + // not end in a newline is not silently given one + let lines = text.split_inclusive('\n').collect(); + + Self { + lines, + index: 0, + sections: Vec::new(), + prose: Vec::new(), + } + } + + fn parse(mut self) -> Result { + while let Some(line) = self.next_line() { + let trimmed = trim_newline(line); + + if let Some(heading) = trimmed.strip_prefix("## ") { + self.finish_prose()?; + let (name, expectation) = parse_heading(heading, self.index)?; + self.push_section(name, expectation)?; + } else if let Some(language) = fence_language(trimmed) { + let content = self.read_fenced_content()?; + self.push_cell(language, content)?; + } else if self.sections.is_empty() && trimmed.starts_with("") { + return; + } + + while let Some(line) = self.next_line() { + if trim_newline(line).ends_with("-->") { + return; + } + } + } + + fn read_fenced_content(&mut self) -> Result { + let opened = self.index; + let mut content = String::new(); + + while let Some(line) = self.next_line() { + if trim_newline(line) == "```" { + return Ok(content); + } + content.push_str(line); + } + + Err(DocumentError { + line: opened, + message: "unterminated fenced cell".to_string(), + }) + } + + fn push_section( + &mut self, + name: String, + expectation: Expectation, + ) -> Result<(), DocumentError> { + if self.sections.iter().any(|section| section.name == name) { + return Err(DocumentError { + line: self.index, + message: format!( + "duplicate test name `{name}`; names must be unique within a file" + ), + }); + } + + self.sections.push(Section { + name, + expectation, + elements: Vec::new(), + }); + + Ok(()) + } + + fn push_cell(&mut self, language: &str, content: String) -> Result<(), DocumentError> { + self.finish_prose()?; + + if language == "r" { + let block = Block { + source: content, + generated: Vec::new(), + }; + return self.push_element(Element::Block(block)); + } + + let cell = match language { + "cst" => Cell::Cst(content), + "diagnostics" => Cell::Diagnostics(content), + other => { + return Err(DocumentError { + line: self.index, + message: format!( + "unknown cell language `{other}`, expected `r`, `cst` or `diagnostics`" + ), + }); + } + }; + + self.push_generated(cell) + } + + fn push_generated(&mut self, cell: Cell) -> Result<(), DocumentError> { + self.finish_prose()?; + + let line = self.index; + let block = self + .sections + .last_mut() + .and_then(|section| section.blocks_mut().next_back()); + + match block { + Some(block) => { + block.generated.push(cell); + Ok(()) + } + None => Err(DocumentError { + line, + message: "generated cell with no ```r block before it".to_string(), + }), + } + } + + fn push_element(&mut self, element: Element) -> Result<(), DocumentError> { + match self.sections.last_mut() { + Some(section) => { + section.elements.push(element); + Ok(()) + } + None => Err(DocumentError { + line: self.index, + message: "content before the first `## ` heading".to_string(), + }), + } + } + + /// Flushes accumulated prose, dropping its boundary blank lines + /// + /// The trim is what makes rendering idempotent: loosely spaced input + /// converges after one `just accept` instead of gaining a blank line on + /// every pass. + fn finish_prose(&mut self) -> Result<(), DocumentError> { + let lines = std::mem::take(&mut self.prose); + let prose = lines.concat(); + let prose = prose.trim_matches('\n'); + + if prose.is_empty() { + return Ok(()); + } + + self.push_element(Element::Prose(prose.to_string())) + } +} + +fn parse_heading(heading: &str, line: usize) -> Result<(String, Expectation), DocumentError> { + let heading = heading.trim(); + + // The marker is the trailing bracketed group, so a name may itself contain + // brackets — `Unsupported Subset2 With `]]` Over Multiple Lines` does + let Some(open) = heading.strip_suffix(']').and_then(|rest| rest.rfind('[')) else { + return Ok((heading.to_string(), Expectation::default())); + }; + + let inside = &heading[open + 1..heading.len() - 1]; + if inside.contains('[') || inside.contains(']') { + return Ok((heading.to_string(), Expectation::default())); + } + + let expectation = Expectation::parse(inside, line)?; + Ok((heading[..open].trim().to_string(), expectation)) +} + +fn fence_language(line: &str) -> Option<&str> { + let language = line.strip_prefix("```")?; + + if language.is_empty() || language.contains(' ') { + None + } else { + Some(language) + } +} + +fn trim_newline(line: &str) -> &str { + line.trim_end_matches('\n').trim_end_matches('\r') +} + +#[cfg(test)] +mod tests { + use super::*; + + fn document(text: &str) -> Document { + Document::parse(text).expect("the document parses") + } + + fn error(text: &str) -> String { + Document::parse(text) + .expect_err("the document is rejected") + .message + } + + const INPUT: &str = "\ +## Numbers + +```r +1 +``` + +## Dollar [error] + +Why this test exists. + +```r +a$ +``` + +```r +b$ +``` +"; + + #[test] + fn parses_headings_markers_and_blocks() { + let document = document(INPUT); + + assert_eq!( + document.shape(), + vec![ + ("Numbers", Expectation::Clean, 1), + ("Dollar", Expectation::Error, 2), + ] + ); + + let sources: Vec<_> = document.sections[1] + .blocks() + .map(|block| block.source.as_str()) + .collect(); + + assert_eq!(sources, vec!["a$\n", "b$\n"]); + } + + /// Prose keeps its place in the element sequence, so rendering can put each + /// block's cells beside it + #[test] + fn prose_interleaves_with_blocks() { + let document = document(INPUT); + + assert!(matches!( + document.sections[1].elements.as_slice(), + [ + Element::Prose(prose), + Element::Block(_), + Element::Block(_) + ] if prose == "Why this test exists." + )); + } + + /// Textual idempotence is the contract, since CI regenerates and asserts a + /// clean tree. A cell whose content does not end its line gains a newline, so + /// the structural round-trip holds only for content that already ends in one. + #[test] + fn round_trips_through_a_snapshot() { + let mut document = document(INPUT); + document.sections[0].blocks_mut().next().unwrap().generated = + vec![Cell::Cst("RRoot { }\n".to_string())]; + document.sections[1].blocks_mut().for_each(|block| { + block.generated = vec![ + Cell::Cst("RBogusExpression { }\n".to_string()), + Cell::Diagnostics("error\n".to_string()), + ] + }); + + let rendered = document.render_snapshot(); + assert_eq!(Document::parse(&rendered).unwrap(), document); + assert_eq!( + Document::parse(&rendered).unwrap().render_snapshot(), + rendered + ); + } + + /// Loosely spaced input must converge after one pass rather than gaining a + /// blank line on every one + #[test] + fn rendering_is_idempotent_over_prose_spacing() { + let loose = "## A\n\n\n\nSpaced out.\n\n\n\n```r\n1\n```\n"; + let rendered = document(loose).render_snapshot(); + + assert_eq!(document(&rendered).render_snapshot(), rendered); + } + + #[test] + fn an_empty_block_survives() { + let document = document("## A\n\n```r\n```\n"); + assert_eq!(document.sections[0].blocks().next().unwrap().source, ""); + + let rendered = document.render_snapshot(); + assert_eq!(Document::parse(&rendered).unwrap(), document); + } + + /// A name may hold brackets of its own; only a trailing group is a marker + #[test] + fn markers_are_the_trailing_bracket_group() { + let document = document("## Subset2 With `]]` (#177) [error]\n\n```r\n1\n```\n"); + + assert_eq!( + document.shape(), + vec![("Subset2 With `]]` (#177)", Expectation::Error, 1)] + ); + } + + #[test] + fn rejects_content_before_the_first_heading() { + assert!(error("Stray prose.\n\n## A\n\n```r\n1\n```\n").contains("before the first")); + } + + #[test] + fn rejects_a_heading_with_no_block() { + assert!(error("## A\n\nJust prose.\n").contains("no ```r block")); + } + + #[test] + fn rejects_a_duplicate_name() { + let text = "## A\n\n```r\n1\n```\n\n## A\n\n```r\n2\n```\n"; + assert!(error(text).contains("duplicate test name")); + } + + #[test] + fn rejects_an_unknown_marker() { + assert!(error("## A [wip]\n\n```r\n1\n```\n").contains("unknown marker")); + } + + #[test] + fn rejects_an_unterminated_cell() { + assert!(error("## A\n\n```r\n1\n").contains("unterminated")); + } +} diff --git a/crates/air_r_parser_tests/src/lib.rs b/crates/air_r_parser_tests/src/lib.rs new file mode 100644 index 00000000..3d843a2f --- /dev/null +++ b/crates/air_r_parser_tests/src/lib.rs @@ -0,0 +1,14 @@ +pub mod check; +pub mod corpus; +pub mod document; +pub mod runner; + +pub use crate::check::check; +pub use crate::document::Block; +pub use crate::document::Cell; +pub use crate::document::Document; +pub use crate::document::DocumentError; +pub use crate::document::Element; +pub use crate::document::Expectation; +pub use crate::document::Section; +pub use crate::runner::run_file; diff --git a/crates/air_r_parser_tests/src/runner.rs b/crates/air_r_parser_tests/src/runner.rs new file mode 100644 index 00000000..d6928ec3 --- /dev/null +++ b/crates/air_r_parser_tests/src/runner.rs @@ -0,0 +1,96 @@ +use crate::check::CheckError; +use crate::check::check; +use crate::corpus; +use crate::document::Document; +use std::fmt::Write as _; +use std::fs; +use std::path::Path; + +pub fn run_file(input_path: &Path) { + let display = corpus::relative(input_path); + let snapshot_path = corpus::snapshot_of(input_path); + + let input = + fs::read_to_string(input_path).unwrap_or_else(|err| panic!("cannot read {display}: {err}")); + let input = Document::parse(&input).unwrap_or_else(|err| panic!("{display}: {err}")); + + let committed = fs::read_to_string(&snapshot_path) + .unwrap_or_else(|_| panic!("{display} has no snapshot. Run `just accept`.")); + + // Marker violations fail before any diffing, so a parser bug is not + // reported as a confusing snapshot mismatch + let mut generated = input.clone(); + let mut failures = Vec::new(); + + for section in &mut generated.sections { + let expectation = section.expectation; + let name = section.name.clone(); + + // A heading may hold several blocks, so the name alone would not say + // which one failed + for (index, block) in section.blocks_mut().enumerate() { + match check(block, expectation) { + Ok(cells) => block.generated = cells, + Err(error) => failures.push((name.clone(), index, error)), + } + } + } + + if !failures.is_empty() { + panic!("{}", render_failures(&display, &failures)); + } + + // Check that the input and the snapshot are structurally the same + let snapshot = Document::parse(&committed) + .unwrap_or_else(|err| panic!("{}: {err}", corpus::relative(&snapshot_path))); + + assert_eq!( + input.shape(), + snapshot.shape(), + "{display} and its snapshot have drifted apart — run `just accept`", + ); + + let rendered = generated.render_snapshot(); + if rendered != committed { + let diff = similar::TextDiff::from_lines(&committed, &rendered); + panic!( + "{}\n{} is out of date. Run `just accept`.", + diff.unified_diff().header("committed", "generated"), + corpus::relative(&snapshot_path), + ); + } +} + +fn render_failures(display: &str, failures: &[(String, usize, CheckError)]) -> String { + let mut out = format!("{} failing block(s) in {display}\n", failures.len()); + + for (name, index, error) in failures { + write!(out, "\n## {name} (block {})\n\n", index + 1).unwrap(); + out.push_str(&render_failure(error)); + } + + out +} + +fn render_failure(error: &CheckError) -> String { + match error { + CheckError::RoundTrip { expected, got } => format!( + "The parse did not round-trip, so a token was dropped or invented.\n\ + input: {expected:?}\n\ + output: {got:?}\n" + ), + CheckError::ExpectedError => { + "Marked `[error]` but produced no error diagnostic.\n".to_string() + } + CheckError::UnexpectedError(diagnostics) => { + format!("Expected a clean parse, but:\n\n{diagnostics}") + } + CheckError::BogusNodes(ast) => { + format!("Bogus nodes in a clean test:\n\n{ast}\n") + } + CheckError::UndiagnosedBogusNode { ranges, ast } => format!( + "Bogus nodes that no diagnostic points at, at {ranges:?}.\n\ + {ast}\n" + ), + } +} diff --git a/crates/air_r_parser_tests/tests/corpus/control/for.md b/crates/air_r_parser_tests/tests/corpus/control/for.md new file mode 100644 index 00000000..95638620 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/control/for.md @@ -0,0 +1,69 @@ +## For + +```r +for (x in y) + f + +for (x in 5:6) { + for (y in ys) { + z + } +} + +for (x in y) for (y in z) x + y + +for (x in y + +) 1 + 1 +``` + +## For no body [error] + +```r +for (i in 1:5) +``` + +## For with comments + +```r +for (i in 1) i + +for ( + i # comment + in # comment2 + 1 # comment3 +) i + +for # dangling1 + ( # dangling2 + a in 1 + ) # dangling3 + a +``` + +## `...` and `..i` as the `for` variable + +Technically allowed! + +```r +for (... in 1:2) print(get("...")) +for (..1 in 1:2) print(get("..1")) +``` + +## A `for` variable that is not a name [error] + +```r +for (1 in x) y +``` + +## A `for` with no variable [error] + +```r +for (in x) y +``` + +## A `for` with no sequence [error] + +```r +for (i in) y +``` diff --git a/crates/air_r_parser_tests/tests/corpus/control/for.snap.md b/crates/air_r_parser_tests/tests/corpus/control/for.snap.md new file mode 100644 index 00000000..4bffa836 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/control/for.snap.md @@ -0,0 +1,538 @@ + + +## For + +```r +for (x in y) + f + +for (x in 5:6) { + for (y in ys) { + z + } +} + +for (x in y) for (y in z) x + y + +for (x in y + +) 1 + 1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@0..4 "for" [] [Whitespace(" ")], + l_paren_token: L_PAREN@4..5 "(" [] [], + variable: RIdentifier { + name_token: IDENT@5..7 "x" [] [Whitespace(" ")], + }, + in_token: IN_KW@7..10 "in" [] [Whitespace(" ")], + sequence: RIdentifier { + name_token: IDENT@10..11 "y" [] [], + }, + r_paren_token: R_PAREN@11..12 ")" [] [], + body: RIdentifier { + name_token: IDENT@12..16 "f" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@16..22 "for" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@22..23 "(" [] [], + variable: RIdentifier { + name_token: IDENT@23..25 "x" [] [Whitespace(" ")], + }, + in_token: IN_KW@25..28 "in" [] [Whitespace(" ")], + sequence: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@28..29 "5" [] [], + }, + operator: COLON@29..30 ":" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@30..31 "6" [] [], + }, + }, + r_paren_token: R_PAREN@31..33 ")" [] [Whitespace(" ")], + body: RBracedStatements { + l_curly_token: L_CURLY@33..34 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@34..41 "for" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@41..42 "(" [] [], + variable: RIdentifier { + name_token: IDENT@42..44 "y" [] [Whitespace(" ")], + }, + in_token: IN_KW@44..47 "in" [] [Whitespace(" ")], + sequence: RIdentifier { + name_token: IDENT@47..49 "ys" [] [], + }, + r_paren_token: R_PAREN@49..51 ")" [] [Whitespace(" ")], + body: RBracedStatements { + l_curly_token: L_CURLY@51..52 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@52..58 "z" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@58..62 "}" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@62..64 "}" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@64..70 "for" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@70..71 "(" [] [], + variable: RIdentifier { + name_token: IDENT@71..73 "x" [] [Whitespace(" ")], + }, + in_token: IN_KW@73..76 "in" [] [Whitespace(" ")], + sequence: RIdentifier { + name_token: IDENT@76..77 "y" [] [], + }, + r_paren_token: R_PAREN@77..79 ")" [] [Whitespace(" ")], + body: RForStatement { + for_token: FOR_KW@79..83 "for" [] [Whitespace(" ")], + l_paren_token: L_PAREN@83..84 "(" [] [], + variable: RIdentifier { + name_token: IDENT@84..86 "y" [] [Whitespace(" ")], + }, + in_token: IN_KW@86..89 "in" [] [Whitespace(" ")], + sequence: RIdentifier { + name_token: IDENT@89..90 "z" [] [], + }, + r_paren_token: R_PAREN@90..92 ")" [] [Whitespace(" ")], + body: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@92..94 "x" [] [Whitespace(" ")], + }, + operator: PLUS@94..96 "+" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@96..97 "y" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@97..103 "for" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@103..104 "(" [] [], + variable: RIdentifier { + name_token: IDENT@104..106 "x" [] [Whitespace(" ")], + }, + in_token: IN_KW@106..109 "in" [] [Whitespace(" ")], + sequence: RIdentifier { + name_token: IDENT@109..110 "y" [] [], + }, + r_paren_token: R_PAREN@110..114 ")" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@114..116 "1" [] [Whitespace(" ")], + }, + operator: PLUS@116..118 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@118..119 "1" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@119..120 "" [Newline("\n")] [], +} +``` + +## For no body [error] + +```r +for (i in 1:5) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@0..4 "for" [] [Whitespace(" ")], + l_paren_token: L_PAREN@4..5 "(" [] [], + variable: RIdentifier { + name_token: IDENT@5..7 "i" [] [Whitespace(" ")], + }, + in_token: IN_KW@7..10 "in" [] [Whitespace(" ")], + sequence: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@10..11 "1" [] [], + }, + operator: COLON@11..12 ":" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@12..13 "5" [] [], + }, + }, + r_paren_token: R_PAREN@13..14 ")" [] [], + body: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@14..15 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a loop body but instead found the end of the file. + + 1 │ for (i in 1:5) + > 2 │ + │ +``` + +## For with comments + +```r +for (i in 1) i + +for ( + i # comment + in # comment2 + 1 # comment3 +) i + +for # dangling1 + ( # dangling2 + a in 1 + ) # dangling3 + a +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@0..4 "for" [] [Whitespace(" ")], + l_paren_token: L_PAREN@4..5 "(" [] [], + variable: RIdentifier { + name_token: IDENT@5..7 "i" [] [Whitespace(" ")], + }, + in_token: IN_KW@7..10 "in" [] [Whitespace(" ")], + sequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@10..11 "1" [] [], + }, + r_paren_token: R_PAREN@11..13 ")" [] [Whitespace(" ")], + body: RIdentifier { + name_token: IDENT@13..14 "i" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@14..20 "for" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@20..21 "(" [] [], + variable: RIdentifier { + name_token: IDENT@21..35 "i" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment")], + }, + in_token: IN_KW@35..51 "in" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")], + sequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@51..66 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")], + }, + r_paren_token: R_PAREN@66..69 ")" [Newline("\n")] [Whitespace(" ")], + body: RIdentifier { + name_token: IDENT@69..70 "i" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@70..87 "for" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# dangling1")], + l_paren_token: L_PAREN@87..103 "(" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# dangling2")], + variable: RIdentifier { + name_token: IDENT@103..110 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + in_token: IN_KW@110..113 "in" [] [Whitespace(" ")], + sequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@113..114 "1" [] [], + }, + r_paren_token: R_PAREN@114..130 ")" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# dangling3")], + body: RIdentifier { + name_token: IDENT@130..134 "a" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@134..135 "" [Newline("\n")] [], +} +``` + +## `...` and `..i` as the `for` variable + +Technically allowed! + +```r +for (... in 1:2) print(get("...")) +for (..1 in 1:2) print(get("..1")) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@0..4 "for" [] [Whitespace(" ")], + l_paren_token: L_PAREN@4..5 "(" [] [], + variable: RDots { + value_token: DOTS@5..9 "..." [] [Whitespace(" ")], + }, + in_token: IN_KW@9..12 "in" [] [Whitespace(" ")], + sequence: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@12..13 "1" [] [], + }, + operator: COLON@13..14 ":" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@14..15 "2" [] [], + }, + }, + r_paren_token: R_PAREN@15..17 ")" [] [Whitespace(" ")], + body: RCall { + function: RIdentifier { + name_token: IDENT@17..22 "print" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@22..23 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RCall { + function: RIdentifier { + name_token: IDENT@23..26 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@26..27 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@27..32 "\"...\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@32..33 ")" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@33..34 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@34..39 "for" [Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@39..40 "(" [] [], + variable: RDotDotI { + value_token: DOTDOTI@40..44 "..1" [] [Whitespace(" ")], + }, + in_token: IN_KW@44..47 "in" [] [Whitespace(" ")], + sequence: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@47..48 "1" [] [], + }, + operator: COLON@48..49 ":" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@49..50 "2" [] [], + }, + }, + r_paren_token: R_PAREN@50..52 ")" [] [Whitespace(" ")], + body: RCall { + function: RIdentifier { + name_token: IDENT@52..57 "print" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@57..58 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RCall { + function: RIdentifier { + name_token: IDENT@58..61 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@61..62 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@62..67 "\"..1\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@67..68 ")" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@68..69 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@69..70 "" [Newline("\n")] [], +} +``` + +## A `for` variable that is not a name [error] + +```r +for (1 in x) y +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@0..4 "for" [] [Whitespace(" ")], + l_paren_token: L_PAREN@4..5 "(" [] [], + variable: RBogusForVariable { + items: [ + R_DOUBLE_LITERAL@5..7 "1" [] [Whitespace(" ")], + ], + }, + in_token: IN_KW@7..10 "in" [] [Whitespace(" ")], + sequence: RIdentifier { + name_token: IDENT@10..11 "x" [] [], + }, + r_paren_token: R_PAREN@11..13 ")" [] [Whitespace(" ")], + body: RIdentifier { + name_token: IDENT@13..14 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@14..15 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a loop variable but instead found '1'. + + > 1 │ for (1 in x) y + │ ^ + 2 │ +``` + +## A `for` with no variable [error] + +```r +for (in x) y +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@0..4 "for" [] [Whitespace(" ")], + l_paren_token: L_PAREN@4..5 "(" [] [], + variable: missing (required), + in_token: IN_KW@5..8 "in" [] [Whitespace(" ")], + sequence: RIdentifier { + name_token: IDENT@8..9 "x" [] [], + }, + r_paren_token: R_PAREN@9..11 ")" [] [Whitespace(" ")], + body: RIdentifier { + name_token: IDENT@11..12 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@12..13 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a loop variable but instead found 'in'. + + > 1 │ for (in x) y + │ ^^ + 2 │ +``` + +## A `for` with no sequence [error] + +```r +for (i in) y +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@0..4 "for" [] [Whitespace(" ")], + l_paren_token: L_PAREN@4..5 "(" [] [], + variable: RIdentifier { + name_token: IDENT@5..7 "i" [] [Whitespace(" ")], + }, + in_token: IN_KW@7..9 "in" [] [], + sequence: missing (required), + r_paren_token: R_PAREN@9..11 ")" [] [Whitespace(" ")], + body: RIdentifier { + name_token: IDENT@11..12 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@12..13 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a loop sequence but instead found ')'. + + > 1 │ for (i in) y + │ ^ + 2 │ +``` diff --git a/crates/air_r_parser_tests/tests/corpus/control/if.md b/crates/air_r_parser_tests/tests/corpus/control/if.md new file mode 100644 index 00000000..c9a5aa03 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/control/if.md @@ -0,0 +1,259 @@ +## If + +```r +if (x) + log(y) + +if (a.b) { + log(c) + d +} + +if ( + x < + y +) { + z +} +``` + +## If-else + +```r +if (x) + y else if (a) + b + +if (x) + y else if (a) + b else d + +if (a) { + c + d +} else { + e +} +``` + +## Complex if expression [error] + +```r +# Invalid at top level due to newline before `else`, so not a real if statement +if (TRUE) { + 1 +} +else { + 2 +} + +# Invalid for same reason as above +if (TRUE) + 1 +else + 2 +``` + +## Complex if expression - 2 + +```r +# Valid inside `{` only due to special `else` handling with newlines +{ + if (TRUE) { + 1 + } + else { + 2 + } +} + +# Valid with comments in special newline territory +{ + if (TRUE) { + 1 + } + # hi there + + # another one! + + else { + 2 + } +} + +# Valid. This test ensures we handle the newline after `1 + 1` correctly (r-lib/tree-sitter-r#125). +{ + if (TRUE) + + 1 + 1 +} + +# Valid. Newlines are allowed between the `else` and the `alternative`, even at top level (r-lib/tree-sitter-r#141). +if (TRUE) { + 1 +} else +{ + 2 +} + +# Valid. Same as above but in `{ }` so it is valid no matter where the newlines are. +{ + if (TRUE) { + 1 + } else + { + 2 + } +} + +# Valid. Newlines and comments are allowed between the `else` and the `alternative`, even at top level. +if (TRUE) { + 1 +} else + +# do this alternative +{ + 2 +} + +# Valid. Newlines are allowed between the `else` and the `alternative`, even at top level. +if (TRUE) 1 else + 2 +``` + +## Identifiers starting with `else` that are not the `else` keyword (r-lib/tree-sitter-r#200, r-lib/tree-sitter-r#499) + +```r +# ASCII letter +{ + if (TRUE) 1 + elseidx <- 1 +} + +# ASCII number +{ + if (TRUE) 1 + else1idx <- 1 +} + +# ASCII `_` +{ + if (TRUE) 1 + else_idx <- 1 +} + +# ASCII `.` +{ + if (TRUE) 1 + else.idx <- 1 +} + +# Non-ASCII, but considered `iswalnum()` in R with UTF-8 locale. +# Our lexer uses `XID_Continue`, which allows this and aligns with R. +{ + if (TRUE) 1 + elseμ <- 1 +} + +# Non-ASCII (U+00B7), rejected by R as syntax error since `iswalnum()` returns false. +# `XID_Continue` allows this, but that's ok since the R side behavior is a +# syntax error. +{ + if (TRUE) 1 + else· <- 1 +} +``` + +## Identifiers starting with `else` that are not the `else` keyword - 2 (r-lib/tree-sitter-r#200) [error] + +```r +# Correctly treats `else.x` as a single identifier, not as `else` followed by `.x`, +# which also throws a diagnostic about a missing statement separator. +# See r-lib/tree-sitter-r#194. +if (TRUE) { + 1 +} else.x +``` + +## If with comments + +```r +if # comment1 + (a) # comment2 + 1 else # comment3 + 2 # comment4 +``` + +## If and else + +```r +if (a) 1 +if (a) 1 else 2 +if (a) 1 else if (b) 2 else 3 +``` + +An `else` binds to the nearest `if`. + +```r +if (a) if (b) c else d +``` + +## A consequence and an alternative stop at the binary `?` + +Both share the rank a function or loop body has, so `if (a) b ? c` must parse as +`(if (a) b) ? c`. + +```r +if (a) b ? c +if (a) b else c ? d +``` + +An assignment binds tighter than either, so the consequence swallows it. + +```r +if (a) b <- c else d +1 + if (a) b +``` + +## A newline before `else` at top level [error] + +Fatal here. The `if` ends at the consequence and the `else` is left over. + +```r +if (a) 1 +else 2 +``` + +## A newline before `else` inside a delimiter + +Newlines carry no meaning inside `(`, `[`, or `[[` + +```r +f(if (a) 1 + else 2) +``` + +```r +x[if (a) 1 + else 2] +``` + +```r +x[[if (a) 1 + else 2]] +``` + +## A newline between `else` and its alternative + +Allowed even at top level, unlike a newline before the `else` itself (r-lib/tree-sitter-r#141). + +```r +if (a) 1 else + 2 +``` + +## An `if` with no condition [error] + +```r +if () 1 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/control/if.snap.md b/crates/air_r_parser_tests/tests/corpus/control/if.snap.md new file mode 100644 index 00000000..143867ec --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/control/if.snap.md @@ -0,0 +1,1663 @@ + + +## If + +```r +if (x) + log(y) + +if (a.b) { + log(c) + d +} + +if ( + x < + y +) { + z +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: RIdentifier { + name_token: IDENT@4..5 "x" [] [], + }, + r_paren_token: R_PAREN@5..6 ")" [] [], + consequence: RCall { + function: RIdentifier { + name_token: IDENT@6..12 "log" [Newline("\n"), Whitespace(" ")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@12..13 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@13..14 "y" [] [], + }, + }, + ], + r_paren_token: R_PAREN@14..15 ")" [] [], + }, + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@15..20 "if" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@20..21 "(" [] [], + condition: RIdentifier { + name_token: IDENT@21..24 "a.b" [] [], + }, + r_paren_token: R_PAREN@24..26 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@26..27 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@27..33 "log" [Newline("\n"), Whitespace(" ")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@33..34 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@34..35 "c" [] [], + }, + }, + ], + r_paren_token: R_PAREN@35..36 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@36..40 "d" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@40..42 "}" [Newline("\n")] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@42..47 "if" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@47..48 "(" [] [], + condition: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@48..53 "x" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: LESS_THAN@53..54 "<" [] [], + right: RIdentifier { + name_token: IDENT@54..58 "y" [Newline("\n"), Whitespace(" ")] [], + }, + }, + r_paren_token: R_PAREN@58..61 ")" [Newline("\n")] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@61..62 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@62..66 "z" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@66..68 "}" [Newline("\n")] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@68..69 "" [Newline("\n")] [], +} +``` + +## If-else + +```r +if (x) + y else if (a) + b + +if (x) + y else if (a) + b else d + +if (a) { + c + d +} else { + e +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: RIdentifier { + name_token: IDENT@4..5 "x" [] [], + }, + r_paren_token: R_PAREN@5..6 ")" [] [], + consequence: RIdentifier { + name_token: IDENT@6..11 "y" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@11..16 "else" [] [Whitespace(" ")], + alternative: RIfStatement { + if_token: IF_KW@16..19 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@19..20 "(" [] [], + condition: RIdentifier { + name_token: IDENT@20..21 "a" [] [], + }, + r_paren_token: R_PAREN@21..22 ")" [] [], + consequence: RIdentifier { + name_token: IDENT@22..26 "b" [Newline("\n"), Whitespace(" ")] [], + }, + else_clause: missing (optional), + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@26..31 "if" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@31..32 "(" [] [], + condition: RIdentifier { + name_token: IDENT@32..33 "x" [] [], + }, + r_paren_token: R_PAREN@33..34 ")" [] [], + consequence: RIdentifier { + name_token: IDENT@34..39 "y" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@39..44 "else" [] [Whitespace(" ")], + alternative: RIfStatement { + if_token: IF_KW@44..47 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@47..48 "(" [] [], + condition: RIdentifier { + name_token: IDENT@48..49 "a" [] [], + }, + r_paren_token: R_PAREN@49..50 ")" [] [], + consequence: RIdentifier { + name_token: IDENT@50..55 "b" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@55..60 "else" [] [Whitespace(" ")], + alternative: RIdentifier { + name_token: IDENT@60..61 "d" [] [], + }, + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@61..66 "if" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@66..67 "(" [] [], + condition: RIdentifier { + name_token: IDENT@67..68 "a" [] [], + }, + r_paren_token: R_PAREN@68..70 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@70..71 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@71..75 "c" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@75..79 "d" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@79..82 "}" [Newline("\n")] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@82..87 "else" [] [Whitespace(" ")], + alternative: RBracedStatements { + l_curly_token: L_CURLY@87..88 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@88..92 "e" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@92..94 "}" [Newline("\n")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@94..95 "" [Newline("\n")] [], +} +``` + +## Complex if expression [error] + +```r +# Invalid at top level due to newline before `else`, so not a real if statement +if (TRUE) { + 1 +} +else { + 2 +} + +# Invalid for same reason as above +if (TRUE) + 1 +else + 2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..83 "if" [Comments("# Invalid at top leve ..."), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@83..84 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@84..88 "TRUE" [] [], + }, + r_paren_token: R_PAREN@88..90 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@90..91 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@91..95 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@95..97 "}" [Newline("\n")] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + ELSE_KW@97..103 "else" [Newline("\n")] [Whitespace(" ")], + L_CURLY@103..104 "{" [] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@104..108 "2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_CURLY@108..110 "}" [Newline("\n")] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@110..150 "if" [Newline("\n"), Newline("\n"), Comments("# Invalid for same re ..."), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@150..151 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@151..155 "TRUE" [] [], + }, + r_paren_token: R_PAREN@155..156 ")" [] [], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@156..160 "1" [Newline("\n"), Whitespace(" ")] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + ELSE_KW@160..165 "else" [Newline("\n")] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@165..169 "2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@169..170 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:5:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found 'else {'. + + 3 │ 1 + 4 │ } + > 5 │ else { + │ ^^^^^^ + 6 │ 2 + 7 │ } + +test.R:7:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '}'. + + 5 │ else { + 6 │ 2 + > 7 │ } + │ ^ + 8 │ + 9 │ # Invalid for same reason as above + +test.R:12:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found 'else'. + + 10 │ if (TRUE) + 11 │ 1 + > 12 │ else + │ ^^^^ + 13 │ 2 + 14 │ +``` + +## Complex if expression - 2 + +```r +# Valid inside `{` only due to special `else` handling with newlines +{ + if (TRUE) { + 1 + } + else { + 2 + } +} + +# Valid with comments in special newline territory +{ + if (TRUE) { + 1 + } + # hi there + + # another one! + + else { + 2 + } +} + +# Valid. This test ensures we handle the newline after `1 + 1` correctly (r-lib/tree-sitter-r#125). +{ + if (TRUE) + + 1 + 1 +} + +# Valid. Newlines are allowed between the `else` and the `alternative`, even at top level (r-lib/tree-sitter-r#141). +if (TRUE) { + 1 +} else +{ + 2 +} + +# Valid. Same as above but in `{ }` so it is valid no matter where the newlines are. +{ + if (TRUE) { + 1 + } else + { + 2 + } +} + +# Valid. Newlines and comments are allowed between the `else` and the `alternative`, even at top level. +if (TRUE) { + 1 +} else + +# do this alternative +{ + 2 +} + +# Valid. Newlines are allowed between the `else` and the `alternative`, even at top level. +if (TRUE) 1 else + 2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..70 "{" [Comments("# Valid inside `{` on ..."), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@70..76 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@76..77 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@77..81 "TRUE" [] [], + }, + r_paren_token: R_PAREN@81..83 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@83..84 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@84..90 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@90..94 "}" [Newline("\n"), Whitespace(" ")] [], + }, + else_clause: RElseClause { + else_token: ELSE_KW@94..102 "else" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternative: RBracedStatements { + l_curly_token: L_CURLY@102..103 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@103..109 "2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@109..113 "}" [Newline("\n"), Whitespace(" ")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@113..115 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@115..169 "{" [Newline("\n"), Newline("\n"), Comments("# Valid with comments ..."), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@169..175 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@175..176 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@176..180 "TRUE" [] [], + }, + r_paren_token: R_PAREN@180..182 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@182..183 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@183..189 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@189..193 "}" [Newline("\n"), Whitespace(" ")] [], + }, + else_clause: RElseClause { + else_token: ELSE_KW@193..233 "else" [Newline("\n"), Whitespace(" "), Comments("# hi there"), Newline("\n"), Newline("\n"), Whitespace(" "), Comments("# another one!"), Newline("\n"), Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternative: RBracedStatements { + l_curly_token: L_CURLY@233..234 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@234..240 "2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@240..244 "}" [Newline("\n"), Whitespace(" ")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@244..246 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@246..349 "{" [Newline("\n"), Newline("\n"), Comments("# Valid. This test en ..."), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@349..355 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@355..356 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@356..360 "TRUE" [] [], + }, + r_paren_token: R_PAREN@360..361 ")" [] [], + consequence: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@361..369 "1" [Newline("\n"), Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: PLUS@369..371 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@371..372 "1" [] [], + }, + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@372..374 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@374..496 "if" [Newline("\n"), Newline("\n"), Comments("# Valid. Newlines are ..."), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@496..497 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@497..501 "TRUE" [] [], + }, + r_paren_token: R_PAREN@501..503 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@503..504 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@504..508 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@508..511 "}" [Newline("\n")] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@511..515 "else" [] [], + alternative: RBracedStatements { + l_curly_token: L_CURLY@515..517 "{" [Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@517..521 "2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@521..523 "}" [Newline("\n")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@523..611 "{" [Newline("\n"), Newline("\n"), Comments("# Valid. Same as abov ..."), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@611..617 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@617..618 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@618..622 "TRUE" [] [], + }, + r_paren_token: R_PAREN@622..624 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@624..625 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@625..631 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@631..636 "}" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@636..640 "else" [] [], + alternative: RBracedStatements { + l_curly_token: L_CURLY@640..644 "{" [Newline("\n"), Whitespace(" ")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@644..650 "2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@650..654 "}" [Newline("\n"), Whitespace(" ")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@654..656 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@656..765 "if" [Newline("\n"), Newline("\n"), Comments("# Valid. Newlines and ..."), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@765..766 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@766..770 "TRUE" [] [], + }, + r_paren_token: R_PAREN@770..772 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@772..773 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@773..777 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@777..780 "}" [Newline("\n")] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@780..784 "else" [] [], + alternative: RBracedStatements { + l_curly_token: L_CURLY@784..809 "{" [Newline("\n"), Newline("\n"), Comments("# do this alternative"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@809..813 "2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@813..815 "}" [Newline("\n")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@815..911 "if" [Newline("\n"), Newline("\n"), Comments("# Valid. Newlines are ..."), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@911..912 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@912..916 "TRUE" [] [], + }, + r_paren_token: R_PAREN@916..918 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@918..920 "1" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@920..924 "else" [] [], + alternative: RDoubleValue { + value_token: R_DOUBLE_LITERAL@924..928 "2" [Newline("\n"), Whitespace(" ")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@928..929 "" [Newline("\n")] [], +} +``` + +## Identifiers starting with `else` that are not the `else` keyword (r-lib/tree-sitter-r#200, r-lib/tree-sitter-r#499) + +```r +# ASCII letter +{ + if (TRUE) 1 + elseidx <- 1 +} + +# ASCII number +{ + if (TRUE) 1 + else1idx <- 1 +} + +# ASCII `_` +{ + if (TRUE) 1 + else_idx <- 1 +} + +# ASCII `.` +{ + if (TRUE) 1 + else.idx <- 1 +} + +# Non-ASCII, but considered `iswalnum()` in R with UTF-8 locale. +# Our lexer uses `XID_Continue`, which allows this and aligns with R. +{ + if (TRUE) 1 + elseμ <- 1 +} + +# Non-ASCII (U+00B7), rejected by R as syntax error since `iswalnum()` returns false. +# `XID_Continue` allows this, but that's ok since the R side behavior is a +# syntax error. +{ + if (TRUE) 1 + else· <- 1 +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..16 "{" [Comments("# ASCII letter"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@16..22 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@22..23 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@23..27 "TRUE" [] [], + }, + r_paren_token: R_PAREN@27..29 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@29..30 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@30..41 "elseidx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: ASSIGN@41..44 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@44..45 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@45..47 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@47..65 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII number"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@65..71 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@71..72 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@72..76 "TRUE" [] [], + }, + r_paren_token: R_PAREN@76..78 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@78..79 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@79..91 "else1idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: ASSIGN@91..94 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@94..95 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@95..97 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@97..112 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII `_`"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@112..118 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@118..119 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@119..123 "TRUE" [] [], + }, + r_paren_token: R_PAREN@123..125 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@125..126 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@126..138 "else_idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: ASSIGN@138..141 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@141..142 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@142..144 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@144..159 "{" [Newline("\n"), Newline("\n"), Comments("# ASCII `.`"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@159..165 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@165..166 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@166..170 "TRUE" [] [], + }, + r_paren_token: R_PAREN@170..172 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@172..173 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@173..185 "else.idx" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: ASSIGN@185..188 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@188..189 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@189..191 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@191..329 "{" [Newline("\n"), Newline("\n"), Comments("# Non-ASCII, but cons ..."), Newline("\n"), Comments("# Our lexer uses `XID ..."), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@329..335 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@335..336 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@336..340 "TRUE" [] [], + }, + r_paren_token: R_PAREN@340..342 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@342..343 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@343..353 "elseμ" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: ASSIGN@353..356 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@356..357 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@357..359 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@359..539 "{" [Newline("\n"), Newline("\n"), Comments("# Non-ASCII (U+00B7), ..."), Newline("\n"), Comments("# `XID_Continue` allo ..."), Newline("\n"), Comments("# syntax error."), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@539..545 "if" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + l_paren_token: L_PAREN@545..546 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@546..550 "TRUE" [] [], + }, + r_paren_token: R_PAREN@550..552 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@552..553 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@553..563 "else·" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: ASSIGN@563..566 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@566..567 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@567..569 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@569..570 "" [Newline("\n")] [], +} +``` + +## Identifiers starting with `else` that are not the `else` keyword - 2 (r-lib/tree-sitter-r#200) [error] + +```r +# Correctly treats `else.x` as a single identifier, not as `else` followed by `.x`, +# which also throws a diagnostic about a missing statement separator. +# See r-lib/tree-sitter-r#194. +if (TRUE) { + 1 +} else.x +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..188 "if" [Comments("# Correctly treats `e ..."), Newline("\n"), Comments("# which also throws a ..."), Newline("\n"), Comments("# See r-lib/tree-sitt ..."), Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@188..189 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@189..193 "TRUE" [] [], + }, + r_paren_token: R_PAREN@193..195 ")" [] [Whitespace(" ")], + consequence: RBracedStatements { + l_curly_token: L_CURLY@195..196 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@196..200 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@200..203 "}" [Newline("\n")] [Whitespace(" ")], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@203..209 "else.x" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@209..210 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:6:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a newline or a `;` between two expressions. + + 4 │ if (TRUE) { + 5 │ 1 + > 6 │ } else.x + │ ^^^^^^ + 7 │ + + i The first expression ends here. + + 2 │ # which also throws a diagnostic about a missing statement separator. + 3 │ # See r-lib/tree-sitter-r#194. + > 4 │ if (TRUE) { + │ ^^^^^^^^^^^ + > 5 │ 1 + > 6 │ } else.x + │ ^ + 7 │ +``` + +## If with comments + +```r +if # comment1 + (a) # comment2 + 1 else # comment3 + 2 # comment4 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..13 "if" [] [Whitespace(" "), Comments("# comment1")], + l_paren_token: L_PAREN@13..17 "(" [Newline("\n"), Whitespace(" ")] [], + condition: RIdentifier { + name_token: IDENT@17..18 "a" [] [], + }, + r_paren_token: R_PAREN@18..30 ")" [] [Whitespace(" "), Comments("# comment2")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@30..35 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@35..50 "else" [] [Whitespace(" "), Comments("# comment3")], + alternative: RDoubleValue { + value_token: R_DOUBLE_LITERAL@50..65 "2" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment4")], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@65..66 "" [Newline("\n")] [], +} +``` + +## If and else + +```r +if (a) 1 +if (a) 1 else 2 +if (a) 1 else if (b) 2 else 3 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: RIdentifier { + name_token: IDENT@4..5 "a" [] [], + }, + r_paren_token: R_PAREN@5..7 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@7..8 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@8..12 "if" [Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@12..13 "(" [] [], + condition: RIdentifier { + name_token: IDENT@13..14 "a" [] [], + }, + r_paren_token: R_PAREN@14..16 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@16..18 "1" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@18..23 "else" [] [Whitespace(" ")], + alternative: RDoubleValue { + value_token: R_DOUBLE_LITERAL@23..24 "2" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@24..28 "if" [Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@28..29 "(" [] [], + condition: RIdentifier { + name_token: IDENT@29..30 "a" [] [], + }, + r_paren_token: R_PAREN@30..32 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@32..34 "1" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@34..39 "else" [] [Whitespace(" ")], + alternative: RIfStatement { + if_token: IF_KW@39..42 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@42..43 "(" [] [], + condition: RIdentifier { + name_token: IDENT@43..44 "b" [] [], + }, + r_paren_token: R_PAREN@44..46 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@46..48 "2" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@48..53 "else" [] [Whitespace(" ")], + alternative: RDoubleValue { + value_token: R_DOUBLE_LITERAL@53..54 "3" [] [], + }, + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@54..55 "" [Newline("\n")] [], +} +``` + +An `else` binds to the nearest `if`. + +```r +if (a) if (b) c else d +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: RIdentifier { + name_token: IDENT@4..5 "a" [] [], + }, + r_paren_token: R_PAREN@5..7 ")" [] [Whitespace(" ")], + consequence: RIfStatement { + if_token: IF_KW@7..10 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@10..11 "(" [] [], + condition: RIdentifier { + name_token: IDENT@11..12 "b" [] [], + }, + r_paren_token: R_PAREN@12..14 ")" [] [Whitespace(" ")], + consequence: RIdentifier { + name_token: IDENT@14..16 "c" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@16..21 "else" [] [Whitespace(" ")], + alternative: RIdentifier { + name_token: IDENT@21..22 "d" [] [], + }, + }, + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@22..23 "" [Newline("\n")] [], +} +``` + +## A consequence and an alternative stop at the binary `?` + +Both share the rank a function or loop body has, so `if (a) b ? c` must parse as +`(if (a) b) ? c`. + +```r +if (a) b ? c +if (a) b else c ? d +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: RIdentifier { + name_token: IDENT@4..5 "a" [] [], + }, + r_paren_token: R_PAREN@5..7 ")" [] [Whitespace(" ")], + consequence: RIdentifier { + name_token: IDENT@7..9 "b" [] [Whitespace(" ")], + }, + else_clause: missing (optional), + }, + operator: WAT@9..11 "?" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@11..12 "c" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIfStatement { + if_token: IF_KW@12..16 "if" [Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@16..17 "(" [] [], + condition: RIdentifier { + name_token: IDENT@17..18 "a" [] [], + }, + r_paren_token: R_PAREN@18..20 ")" [] [Whitespace(" ")], + consequence: RIdentifier { + name_token: IDENT@20..22 "b" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@22..27 "else" [] [Whitespace(" ")], + alternative: RIdentifier { + name_token: IDENT@27..29 "c" [] [Whitespace(" ")], + }, + }, + }, + operator: WAT@29..31 "?" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@31..32 "d" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@32..33 "" [Newline("\n")] [], +} +``` + +An assignment binds tighter than either, so the consequence swallows it. + +```r +if (a) b <- c else d +1 + if (a) b +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: RIdentifier { + name_token: IDENT@4..5 "a" [] [], + }, + r_paren_token: R_PAREN@5..7 ")" [] [Whitespace(" ")], + consequence: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@7..9 "b" [] [Whitespace(" ")], + }, + operator: ASSIGN@9..12 "<-" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@12..14 "c" [] [Whitespace(" ")], + }, + }, + else_clause: RElseClause { + else_token: ELSE_KW@14..19 "else" [] [Whitespace(" ")], + alternative: RIdentifier { + name_token: IDENT@19..20 "d" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@20..23 "1" [Newline("\n")] [Whitespace(" ")], + }, + operator: PLUS@23..25 "+" [] [Whitespace(" ")], + right: RIfStatement { + if_token: IF_KW@25..28 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@28..29 "(" [] [], + condition: RIdentifier { + name_token: IDENT@29..30 "a" [] [], + }, + r_paren_token: R_PAREN@30..32 ")" [] [Whitespace(" ")], + consequence: RIdentifier { + name_token: IDENT@32..33 "b" [] [], + }, + else_clause: missing (optional), + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@33..34 "" [Newline("\n")] [], +} +``` + +## A newline before `else` at top level [error] + +Fatal here. The `if` ends at the consequence and the `else` is left over. + +```r +if (a) 1 +else 2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: RIdentifier { + name_token: IDENT@4..5 "a" [] [], + }, + r_paren_token: R_PAREN@5..7 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@7..8 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + ELSE_KW@8..14 "else" [Newline("\n")] [Whitespace(" ")], + R_DOUBLE_LITERAL@14..15 "2" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@15..16 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found 'else 2'. + + 1 │ if (a) 1 + > 2 │ else 2 + │ ^^^^^^ + 3 │ +``` + +## A newline before `else` inside a delimiter + +Newlines carry no meaning inside `(`, `[`, or `[[` + +```r +f(if (a) 1 + else 2) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..1 "f" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIfStatement { + if_token: IF_KW@2..5 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@5..6 "(" [] [], + condition: RIdentifier { + name_token: IDENT@6..7 "a" [] [], + }, + r_paren_token: R_PAREN@7..9 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@9..10 "1" [] [], + }, + else_clause: RElseClause { + else_token: ELSE_KW@10..18 "else" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternative: RDoubleValue { + value_token: R_DOUBLE_LITERAL@18..19 "2" [] [], + }, + }, + }, + }, + ], + r_paren_token: R_PAREN@19..20 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@20..21 "" [Newline("\n")] [], +} +``` + +```r +x[if (a) 1 + else 2] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@1..2 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIfStatement { + if_token: IF_KW@2..5 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@5..6 "(" [] [], + condition: RIdentifier { + name_token: IDENT@6..7 "a" [] [], + }, + r_paren_token: R_PAREN@7..9 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@9..10 "1" [] [], + }, + else_clause: RElseClause { + else_token: ELSE_KW@10..18 "else" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternative: RDoubleValue { + value_token: R_DOUBLE_LITERAL@18..19 "2" [] [], + }, + }, + }, + }, + ], + r_brack_token: R_BRACK@19..20 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@20..21 "" [Newline("\n")] [], +} +``` + +```r +x[[if (a) 1 + else 2]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIfStatement { + if_token: IF_KW@3..6 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@6..7 "(" [] [], + condition: RIdentifier { + name_token: IDENT@7..8 "a" [] [], + }, + r_paren_token: R_PAREN@8..10 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@10..11 "1" [] [], + }, + else_clause: RElseClause { + else_token: ELSE_KW@11..19 "else" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + alternative: RDoubleValue { + value_token: R_DOUBLE_LITERAL@19..20 "2" [] [], + }, + }, + }, + }, + ], + r_brack2_token: R_BRACK2@20..22 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@22..23 "" [Newline("\n")] [], +} +``` + +## A newline between `else` and its alternative + +Allowed even at top level, unlike a newline before the `else` itself (r-lib/tree-sitter-r#141). + +```r +if (a) 1 else + 2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: RIdentifier { + name_token: IDENT@4..5 "a" [] [], + }, + r_paren_token: R_PAREN@5..7 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@7..9 "1" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@9..13 "else" [] [], + alternative: RDoubleValue { + value_token: R_DOUBLE_LITERAL@13..17 "2" [Newline("\n"), Whitespace(" ")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@17..18 "" [Newline("\n")] [], +} +``` + +## An `if` with no condition [error] + +```r +if () 1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@0..3 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@3..4 "(" [] [], + condition: missing (required), + r_paren_token: R_PAREN@4..6 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@6..7 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a condition but instead found ')'. + + > 1 │ if () 1 + │ ^ + 2 │ +``` diff --git a/crates/air_r_parser_tests/tests/corpus/control/repeat.md b/crates/air_r_parser_tests/tests/corpus/control/repeat.md new file mode 100644 index 00000000..bcf576dc --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/control/repeat.md @@ -0,0 +1,22 @@ +## Repeat + +```r +repeat 1 +``` + +## Repeat no body [error] + +```r +repeat +``` + +## Repeat with braces + +```r +repeat { +} + +repeat { + 1 + 1 +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/control/repeat.snap.md b/crates/air_r_parser_tests/tests/corpus/control/repeat.snap.md new file mode 100644 index 00000000..4925c399 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/control/repeat.snap.md @@ -0,0 +1,112 @@ + + +## Repeat + +```r +repeat 1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RRepeatStatement { + repeat_token: REPEAT_KW@0..7 "repeat" [] [Whitespace(" ")], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@7..8 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@8..9 "" [Newline("\n")] [], +} +``` + +## Repeat no body [error] + +```r +repeat +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RRepeatStatement { + repeat_token: REPEAT_KW@0..6 "repeat" [] [], + body: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@6..7 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a loop body but instead found the end of the file. + + 1 │ repeat + > 2 │ + │ +``` + +## Repeat with braces + +```r +repeat { +} + +repeat { + 1 + 1 +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RRepeatStatement { + repeat_token: REPEAT_KW@0..7 "repeat" [] [Whitespace(" ")], + body: RBracedStatements { + l_curly_token: L_CURLY@7..8 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@8..10 "}" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RRepeatStatement { + repeat_token: REPEAT_KW@10..19 "repeat" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + body: RBracedStatements { + l_curly_token: L_CURLY@19..20 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@20..25 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: PLUS@25..27 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@27..28 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@28..30 "}" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@30..31 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/control/while.md b/crates/air_r_parser_tests/tests/corpus/control/while.md new file mode 100644 index 00000000..06b1b5b7 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/control/while.md @@ -0,0 +1,43 @@ +## While + +```r +while(TRUE) + bar + +while(x > 0) + x <- x - 1 + +while(TRUE) + break + +while(TRUE) + next +``` + +## While no body [error] + +```r +while (a < b) +``` + +## While with braces + +```r +while(TRUE) FALSE + +while(TRUE) {} + +while(a) { + +} + +while(fn()) { + +} +``` + +## A `while` with no condition [error] + +```r +while () b +``` diff --git a/crates/air_r_parser_tests/tests/corpus/control/while.snap.md b/crates/air_r_parser_tests/tests/corpus/control/while.snap.md new file mode 100644 index 00000000..ab5ba8d2 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/control/while.snap.md @@ -0,0 +1,274 @@ + + +## While + +```r +while(TRUE) + bar + +while(x > 0) + x <- x - 1 + +while(TRUE) + break + +while(TRUE) + next +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@0..5 "while" [] [], + l_paren_token: L_PAREN@5..6 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@6..10 "TRUE" [] [], + }, + r_paren_token: R_PAREN@10..11 ")" [] [], + body: RIdentifier { + name_token: IDENT@11..17 "bar" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@17..24 "while" [Newline("\n"), Newline("\n")] [], + l_paren_token: L_PAREN@24..25 "(" [] [], + condition: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@25..27 "x" [] [Whitespace(" ")], + }, + operator: GREATER_THAN@27..29 ">" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@29..30 "0" [] [], + }, + }, + r_paren_token: R_PAREN@30..31 ")" [] [], + body: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@31..36 "x" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: ASSIGN@36..39 "<-" [] [Whitespace(" ")], + right: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@39..41 "x" [] [Whitespace(" ")], + }, + operator: MINUS@41..43 "-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@43..44 "1" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@44..51 "while" [Newline("\n"), Newline("\n")] [], + l_paren_token: L_PAREN@51..52 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@52..56 "TRUE" [] [], + }, + r_paren_token: R_PAREN@56..57 ")" [] [], + body: RBreakExpression { + break_token: BREAK_KW@57..65 "break" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@65..72 "while" [Newline("\n"), Newline("\n")] [], + l_paren_token: L_PAREN@72..73 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@73..77 "TRUE" [] [], + }, + r_paren_token: R_PAREN@77..78 ")" [] [], + body: RNextExpression { + next_token: NEXT_KW@78..85 "next" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@85..86 "" [Newline("\n")] [], +} +``` + +## While no body [error] + +```r +while (a < b) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@0..6 "while" [] [Whitespace(" ")], + l_paren_token: L_PAREN@6..7 "(" [] [], + condition: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@7..9 "a" [] [Whitespace(" ")], + }, + operator: LESS_THAN@9..11 "<" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@11..12 "b" [] [], + }, + }, + r_paren_token: R_PAREN@12..13 ")" [] [], + body: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@13..14 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a loop body but instead found the end of the file. + + 1 │ while (a < b) + > 2 │ + │ +``` + +## While with braces + +```r +while(TRUE) FALSE + +while(TRUE) {} + +while(a) { + +} + +while(fn()) { + +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@0..5 "while" [] [], + l_paren_token: L_PAREN@5..6 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@6..10 "TRUE" [] [], + }, + r_paren_token: R_PAREN@10..12 ")" [] [Whitespace(" ")], + body: RFalseExpression { + false_token: FALSE_KW@12..17 "FALSE" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@17..24 "while" [Newline("\n"), Newline("\n")] [], + l_paren_token: L_PAREN@24..25 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@25..29 "TRUE" [] [], + }, + r_paren_token: R_PAREN@29..31 ")" [] [Whitespace(" ")], + body: RBracedStatements { + l_curly_token: L_CURLY@31..32 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@32..33 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@33..40 "while" [Newline("\n"), Newline("\n")] [], + l_paren_token: L_PAREN@40..41 "(" [] [], + condition: RIdentifier { + name_token: IDENT@41..42 "a" [] [], + }, + r_paren_token: R_PAREN@42..44 ")" [] [Whitespace(" ")], + body: RBracedStatements { + l_curly_token: L_CURLY@44..45 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@45..48 "}" [Newline("\n"), Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@48..55 "while" [Newline("\n"), Newline("\n")] [], + l_paren_token: L_PAREN@55..56 "(" [] [], + condition: RCall { + function: RIdentifier { + name_token: IDENT@56..58 "fn" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@58..59 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@59..60 ")" [] [], + }, + }, + r_paren_token: R_PAREN@60..62 ")" [] [Whitespace(" ")], + body: RBracedStatements { + l_curly_token: L_CURLY@62..63 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@63..66 "}" [Newline("\n"), Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@66..67 "" [Newline("\n")] [], +} +``` + +## A `while` with no condition [error] + +```r +while () b +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@0..6 "while" [] [Whitespace(" ")], + l_paren_token: L_PAREN@6..7 "(" [] [], + condition: missing (required), + r_paren_token: R_PAREN@7..9 ")" [] [Whitespace(" ")], + body: RIdentifier { + name_token: IDENT@9..10 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@10..11 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:8 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a condition but instead found ')'. + + > 1 │ while () b + │ ^ + 2 │ +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/assignment.md b/crates/air_r_parser_tests/tests/corpus/expressions/assignment.md new file mode 100644 index 00000000..028d2bc8 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/assignment.md @@ -0,0 +1,12 @@ +## Assignment + +```r +x <- 1 +x = 1 +x := 1 +x <<- 1 +1 ->> x +1 -> x +x <- y(1) +y(1) -> x +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/assignment.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/assignment.snap.md new file mode 100644 index 00000000..8567d5bd --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/assignment.snap.md @@ -0,0 +1,147 @@ + + +## Assignment + +```r +x <- 1 +x = 1 +x := 1 +x <<- 1 +1 ->> x +1 -> x +x <- y(1) +y(1) -> x +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "x" [] [Whitespace(" ")], + }, + operator: ASSIGN@2..5 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..6 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@6..9 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: EQUAL@9..11 "=" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@11..12 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@12..15 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: WALRUS@15..18 ":=" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@18..19 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@19..22 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: SUPER_ASSIGN@22..26 "<<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@26..27 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@27..30 "1" [Newline("\n")] [Whitespace(" ")], + }, + operator: SUPER_ASSIGN_RIGHT@30..34 "->>" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@34..35 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@35..38 "1" [Newline("\n")] [Whitespace(" ")], + }, + operator: ASSIGN_RIGHT@38..41 "->" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@41..42 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@42..45 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: ASSIGN@45..48 "<-" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@48..49 "y" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@49..50 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@50..51 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@51..52 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RCall { + function: RIdentifier { + name_token: IDENT@52..54 "y" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@54..55 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@55..56 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@56..58 ")" [] [Whitespace(" ")], + }, + }, + operator: ASSIGN_RIGHT@58..61 "->" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@61..62 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@62..63 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser/tests/snapshots/ok/binary_expressions.R b/crates/air_r_parser_tests/tests/corpus/expressions/binary.md similarity index 51% rename from crates/air_r_parser/tests/snapshots/ok/binary_expressions.R rename to crates/air_r_parser_tests/tests/corpus/expressions/binary.md index c5295b90..8333baf7 100644 --- a/crates/air_r_parser/tests/snapshots/ok/binary_expressions.R +++ b/crates/air_r_parser_tests/tests/corpus/expressions/binary.md @@ -1,3 +1,44 @@ +## Relational + +```r +a == b +a > b +a < b +a >= b +a <= b +a != b + +a == + b +``` + +## Arithmetic operator + +```r +a + b +a - b +a * b +a / b +a ^ b +a ** b + +a + + b + +a * + b +``` + +## Not an arithmetic operator + +```r +a + + b +``` + +## Binary operators + +```r 1 ? 2 1 ~ 2 1 <- 2 @@ -38,3 +79,4 @@ # comment1 2 # comment2 +``` diff --git a/crates/air_r_parser/tests/snapshots/ok/binary_expressions.R.snap b/crates/air_r_parser_tests/tests/corpus/expressions/binary.snap.md similarity index 59% rename from crates/air_r_parser/tests/snapshots/ok/binary_expressions.R.snap rename to crates/air_r_parser_tests/tests/corpus/expressions/binary.snap.md index 517ac16b..365f56b3 100644 --- a/crates/air_r_parser/tests/snapshots/ok/binary_expressions.R.snap +++ b/crates/air_r_parser_tests/tests/corpus/expressions/binary.snap.md @@ -1,10 +1,268 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input + -```R +## Relational + +```r +a == b +a > b +a < b +a >= b +a <= b +a != b + +a == + b +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "a" [] [Whitespace(" ")], + }, + operator: EQUAL2@2..5 "==" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@5..6 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@6..9 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: GREATER_THAN@9..11 ">" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@11..12 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@12..15 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: LESS_THAN@15..17 "<" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@17..18 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@18..21 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: GREATER_THAN_OR_EQUAL_TO@21..24 ">=" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@24..25 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@25..28 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: LESS_THAN_OR_EQUAL_TO@28..31 "<=" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@31..32 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@32..35 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: NOT_EQUAL@35..38 "!=" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@38..39 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@39..43 "a" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + operator: EQUAL2@43..45 "==" [] [], + right: RIdentifier { + name_token: IDENT@45..49 "b" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@49..50 "" [Newline("\n")] [], +} +``` + +## Arithmetic operator + +```r +a + b +a - b +a * b +a / b +a ^ b +a ** b + +a + + b + +a * + b +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "a" [] [Whitespace(" ")], + }, + operator: PLUS@2..4 "+" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@4..5 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@5..8 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: MINUS@8..10 "-" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@10..11 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@11..14 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: MULTIPLY@14..16 "*" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@16..17 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@17..20 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: DIVIDE@20..22 "/" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@22..23 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@23..26 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: EXPONENTIATE@26..28 "^" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@28..29 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@29..32 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: EXPONENTIATE2@32..35 "**" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@35..36 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@36..40 "a" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + operator: PLUS@40..41 "+" [] [], + right: RIdentifier { + name_token: IDENT@41..45 "b" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@45..49 "a" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + operator: MULTIPLY@49..50 "*" [] [], + right: RIdentifier { + name_token: IDENT@50..54 "b" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@54..55 "" [Newline("\n")] [], +} +``` + +## Not an arithmetic operator + +```r +a + + b +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: PLUS@1..6 "+" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + argument: RIdentifier { + name_token: IDENT@6..7 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +## Binary operators + +```r 1 ? 2 1 ~ 2 1 <- 2 @@ -45,13 +303,9 @@ expression: snapshot # comment1 2 # comment2 - ``` - -## AST - -``` +```cst RRoot { bom_token: missing (optional), statements: RStatementList [ @@ -455,277 +709,3 @@ RRoot { eof_token: EOF@258..259 "" [Newline("\n")] [], } ``` - -## CST - -``` -0: R_ROOT@0..259 - 0: (empty) - 1: R_STATEMENT_LIST@0..258 - 0: R_EXPRESSION_STATEMENT@0..5 - 0: R_BINARY_EXPRESSION@0..5 - 0: R_DOUBLE_VALUE@0..2 - 0: R_DOUBLE_LITERAL@0..2 "1" [] [Whitespace(" ")] - 1: WAT@2..4 "?" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@4..5 - 0: R_DOUBLE_LITERAL@4..5 "2" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@5..11 - 0: R_BINARY_EXPRESSION@5..11 - 0: R_DOUBLE_VALUE@5..8 - 0: R_DOUBLE_LITERAL@5..8 "1" [Newline("\n")] [Whitespace(" ")] - 1: TILDE@8..10 "~" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@10..11 - 0: R_DOUBLE_LITERAL@10..11 "2" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@11..18 - 0: R_BINARY_EXPRESSION@11..18 - 0: R_DOUBLE_VALUE@11..14 - 0: R_DOUBLE_LITERAL@11..14 "1" [Newline("\n")] [Whitespace(" ")] - 1: ASSIGN@14..17 "<-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@17..18 - 0: R_DOUBLE_LITERAL@17..18 "2" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@18..26 - 0: R_BINARY_EXPRESSION@18..26 - 0: R_DOUBLE_VALUE@18..21 - 0: R_DOUBLE_LITERAL@18..21 "1" [Newline("\n")] [Whitespace(" ")] - 1: SUPER_ASSIGN@21..25 "<<-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@25..26 - 0: R_DOUBLE_LITERAL@25..26 "2" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@26..33 - 0: R_BINARY_EXPRESSION@26..33 - 0: R_DOUBLE_VALUE@26..29 - 0: R_DOUBLE_LITERAL@26..29 "1" [Newline("\n")] [Whitespace(" ")] - 1: WALRUS@29..32 ":=" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@32..33 - 0: R_DOUBLE_LITERAL@32..33 "2" [] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@33..40 - 0: R_BINARY_EXPRESSION@33..40 - 0: R_DOUBLE_VALUE@33..36 - 0: R_DOUBLE_LITERAL@33..36 "1" [Newline("\n")] [Whitespace(" ")] - 1: ASSIGN_RIGHT@36..39 "->" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@39..40 - 0: R_DOUBLE_LITERAL@39..40 "2" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@40..48 - 0: R_BINARY_EXPRESSION@40..48 - 0: R_DOUBLE_VALUE@40..43 - 0: R_DOUBLE_LITERAL@40..43 "1" [Newline("\n")] [Whitespace(" ")] - 1: SUPER_ASSIGN_RIGHT@43..47 "->>" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@47..48 - 0: R_DOUBLE_LITERAL@47..48 "2" [] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@48..54 - 0: R_BINARY_EXPRESSION@48..54 - 0: R_DOUBLE_VALUE@48..51 - 0: R_DOUBLE_LITERAL@48..51 "1" [Newline("\n")] [Whitespace(" ")] - 1: EQUAL@51..53 "=" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@53..54 - 0: R_DOUBLE_LITERAL@53..54 "2" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@54..60 - 0: R_BINARY_EXPRESSION@54..60 - 0: R_DOUBLE_VALUE@54..57 - 0: R_DOUBLE_LITERAL@54..57 "1" [Newline("\n")] [Whitespace(" ")] - 1: OR@57..59 "|" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@59..60 - 0: R_DOUBLE_LITERAL@59..60 "2" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@60..66 - 0: R_BINARY_EXPRESSION@60..66 - 0: R_DOUBLE_VALUE@60..63 - 0: R_DOUBLE_LITERAL@60..63 "1" [Newline("\n")] [Whitespace(" ")] - 1: AND@63..65 "&" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@65..66 - 0: R_DOUBLE_LITERAL@65..66 "2" [] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@66..73 - 0: R_BINARY_EXPRESSION@66..73 - 0: R_DOUBLE_VALUE@66..69 - 0: R_DOUBLE_LITERAL@66..69 "1" [Newline("\n")] [Whitespace(" ")] - 1: OR2@69..72 "||" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@72..73 - 0: R_DOUBLE_LITERAL@72..73 "2" [] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@73..80 - 0: R_BINARY_EXPRESSION@73..80 - 0: R_DOUBLE_VALUE@73..76 - 0: R_DOUBLE_LITERAL@73..76 "1" [Newline("\n")] [Whitespace(" ")] - 1: AND2@76..79 "&&" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@79..80 - 0: R_DOUBLE_LITERAL@79..80 "2" [] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@80..86 - 0: R_BINARY_EXPRESSION@80..86 - 0: R_DOUBLE_VALUE@80..83 - 0: R_DOUBLE_LITERAL@80..83 "1" [Newline("\n")] [Whitespace(" ")] - 1: LESS_THAN@83..85 "<" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@85..86 - 0: R_DOUBLE_LITERAL@85..86 "2" [] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@86..93 - 0: R_BINARY_EXPRESSION@86..93 - 0: R_DOUBLE_VALUE@86..89 - 0: R_DOUBLE_LITERAL@86..89 "1" [Newline("\n")] [Whitespace(" ")] - 1: LESS_THAN_OR_EQUAL_TO@89..92 "<=" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@92..93 - 0: R_DOUBLE_LITERAL@92..93 "2" [] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@93..99 - 0: R_BINARY_EXPRESSION@93..99 - 0: R_DOUBLE_VALUE@93..96 - 0: R_DOUBLE_LITERAL@93..96 "1" [Newline("\n")] [Whitespace(" ")] - 1: GREATER_THAN@96..98 ">" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@98..99 - 0: R_DOUBLE_LITERAL@98..99 "2" [] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@99..106 - 0: R_BINARY_EXPRESSION@99..106 - 0: R_DOUBLE_VALUE@99..102 - 0: R_DOUBLE_LITERAL@99..102 "1" [Newline("\n")] [Whitespace(" ")] - 1: GREATER_THAN_OR_EQUAL_TO@102..105 ">=" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@105..106 - 0: R_DOUBLE_LITERAL@105..106 "2" [] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@106..113 - 0: R_BINARY_EXPRESSION@106..113 - 0: R_DOUBLE_VALUE@106..109 - 0: R_DOUBLE_LITERAL@106..109 "1" [Newline("\n")] [Whitespace(" ")] - 1: EQUAL2@109..112 "==" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@112..113 - 0: R_DOUBLE_LITERAL@112..113 "2" [] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@113..120 - 0: R_BINARY_EXPRESSION@113..120 - 0: R_DOUBLE_VALUE@113..116 - 0: R_DOUBLE_LITERAL@113..116 "1" [Newline("\n")] [Whitespace(" ")] - 1: NOT_EQUAL@116..119 "!=" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@119..120 - 0: R_DOUBLE_LITERAL@119..120 "2" [] [] - 1: (empty) - 18: R_EXPRESSION_STATEMENT@120..126 - 0: R_BINARY_EXPRESSION@120..126 - 0: R_DOUBLE_VALUE@120..123 - 0: R_DOUBLE_LITERAL@120..123 "1" [Newline("\n")] [Whitespace(" ")] - 1: PLUS@123..125 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@125..126 - 0: R_DOUBLE_LITERAL@125..126 "2" [] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@126..132 - 0: R_BINARY_EXPRESSION@126..132 - 0: R_DOUBLE_VALUE@126..129 - 0: R_DOUBLE_LITERAL@126..129 "1" [Newline("\n")] [Whitespace(" ")] - 1: MINUS@129..131 "-" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@131..132 - 0: R_DOUBLE_LITERAL@131..132 "2" [] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@132..138 - 0: R_BINARY_EXPRESSION@132..138 - 0: R_DOUBLE_VALUE@132..135 - 0: R_DOUBLE_LITERAL@132..135 "1" [Newline("\n")] [Whitespace(" ")] - 1: MULTIPLY@135..137 "*" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@137..138 - 0: R_DOUBLE_LITERAL@137..138 "2" [] [] - 1: (empty) - 21: R_EXPRESSION_STATEMENT@138..144 - 0: R_BINARY_EXPRESSION@138..144 - 0: R_DOUBLE_VALUE@138..141 - 0: R_DOUBLE_LITERAL@138..141 "1" [Newline("\n")] [Whitespace(" ")] - 1: DIVIDE@141..143 "/" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@143..144 - 0: R_DOUBLE_LITERAL@143..144 "2" [] [] - 1: (empty) - 22: R_EXPRESSION_STATEMENT@144..151 - 0: R_BINARY_EXPRESSION@144..151 - 0: R_DOUBLE_VALUE@144..147 - 0: R_DOUBLE_LITERAL@144..147 "1" [Newline("\n")] [Whitespace(" ")] - 1: EXPONENTIATE2@147..150 "**" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@150..151 - 0: R_DOUBLE_LITERAL@150..151 "2" [] [] - 1: (empty) - 23: R_EXPRESSION_STATEMENT@151..157 - 0: R_BINARY_EXPRESSION@151..157 - 0: R_DOUBLE_VALUE@151..154 - 0: R_DOUBLE_LITERAL@151..154 "1" [Newline("\n")] [Whitespace(" ")] - 1: EXPONENTIATE@154..156 "^" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@156..157 - 0: R_DOUBLE_LITERAL@156..157 "2" [] [] - 1: (empty) - 24: R_EXPRESSION_STATEMENT@157..164 - 0: R_BINARY_EXPRESSION@157..164 - 0: R_DOUBLE_VALUE@157..160 - 0: R_DOUBLE_LITERAL@157..160 "1" [Newline("\n")] [Whitespace(" ")] - 1: PIPE@160..163 "|>" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@163..164 - 0: R_DOUBLE_LITERAL@163..164 "2" [] [] - 1: (empty) - 25: R_EXPRESSION_STATEMENT@164..172 - 0: R_BINARY_EXPRESSION@164..172 - 0: R_DOUBLE_VALUE@164..167 - 0: R_DOUBLE_LITERAL@164..167 "1" [Newline("\n")] [Whitespace(" ")] - 1: SPECIAL@167..171 "%>%" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@171..172 - 0: R_DOUBLE_LITERAL@171..172 "2" [] [] - 1: (empty) - 26: R_EXPRESSION_STATEMENT@172..176 - 0: R_BINARY_EXPRESSION@172..176 - 0: R_DOUBLE_VALUE@172..174 - 0: R_DOUBLE_LITERAL@172..174 "1" [Newline("\n")] [] - 1: COLON@174..175 ":" [] [] - 2: R_DOUBLE_VALUE@175..176 - 0: R_DOUBLE_LITERAL@175..176 "2" [] [] - 1: (empty) - 27: R_EXPRESSION_STATEMENT@176..191 - 0: R_BINARY_EXPRESSION@176..191 - 0: R_BINARY_EXPRESSION@176..188 - 0: R_BINARY_EXPRESSION@176..184 - 0: R_DOUBLE_VALUE@176..180 - 0: R_DOUBLE_LITERAL@176..180 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: PLUS@180..182 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@182..184 - 0: R_DOUBLE_LITERAL@182..184 "2" [] [Whitespace(" ")] - 1: PLUS@184..186 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@186..188 - 0: R_DOUBLE_LITERAL@186..188 "3" [] [Whitespace(" ")] - 1: PLUS@188..190 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@190..191 - 0: R_DOUBLE_LITERAL@190..191 "4" [] [] - 1: (empty) - 28: R_EXPRESSION_STATEMENT@191..204 - 0: R_BINARY_EXPRESSION@191..204 - 0: R_BINARY_EXPRESSION@191..201 - 0: R_DOUBLE_VALUE@191..195 - 0: R_DOUBLE_LITERAL@191..195 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: PLUS@195..197 "+" [] [Whitespace(" ")] - 2: R_BINARY_EXPRESSION@197..201 - 0: R_DOUBLE_VALUE@197..198 - 0: R_DOUBLE_LITERAL@197..198 "2" [] [] - 1: EXPONENTIATE@198..199 "^" [] [] - 2: R_DOUBLE_VALUE@199..201 - 0: R_DOUBLE_LITERAL@199..201 "3" [] [Whitespace(" ")] - 1: PLUS@201..203 "+" [] [Whitespace(" ")] - 2: R_DOUBLE_VALUE@203..204 - 0: R_DOUBLE_LITERAL@203..204 "4" [] [] - 1: (empty) - 29: R_EXPRESSION_STATEMENT@204..223 - 0: R_BINARY_EXPRESSION@204..223 - 0: R_DOUBLE_VALUE@204..208 - 0: R_DOUBLE_LITERAL@204..208 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: PLUS@208..219 "+" [] [Whitespace(" "), Comments("# comment")] - 2: R_DOUBLE_VALUE@219..223 - 0: R_DOUBLE_LITERAL@219..223 "2" [Newline("\n"), Whitespace(" ")] [] - 1: (empty) - 30: R_EXPRESSION_STATEMENT@223..258 - 0: R_BINARY_EXPRESSION@223..258 - 0: R_DOUBLE_VALUE@223..227 - 0: R_DOUBLE_LITERAL@223..227 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ")] - 1: PLUS@227..228 "+" [] [] - 2: R_DOUBLE_VALUE@228..258 - 0: R_DOUBLE_LITERAL@228..258 "2" [Newline("\n"), Newline("\n"), Whitespace(" "), Comments("# comment1"), Newline("\n"), Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")] - 1: (empty) - 2: EOF@258..259 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/call.md b/crates/air_r_parser_tests/tests/corpus/expressions/call.md new file mode 100644 index 00000000..19e67888 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/call.md @@ -0,0 +1,244 @@ +## Calls + +```r +f() +f(,) +f(,,) +f(x,) +f(x,,) +f(,x) +f(,,x) +f(x,,y) +f(,,x,,y,,) +f(x=,) +f("x"=,) +f(,y=) + +f(x) +f(1+1) +f(1 ~ 1) +f(x, y) +f(x, y = 2) +f(x = 1 + 1) +f(x, y =) +f(f2(x, y)) + +# Dots as unnamed and named argument +f(...) +f(, ..., 1) +f(... = 1) +f(... = ,) +f(... = ...) + +# `..i` as unnamed and named argument +f(..1) +f(, ..1, 1) +f(..1 = 1) +f(..1 = ,) +f(..1 = ..1) +``` + +## Calls with NULL as argument name + +```r +fn(NULL =) +fn(NULL =,) +fn(NULL = 1) + +# This was likely added to R to support this case for `switch()` +switch(typeof(x), NULL = this, integer = that) +``` + +## Calls with `return` as argument name + +```r +# `return` is not a reserved word in R (it's not in `?Reserved`), so it is +# allowed as an argument name (r-lib/tree-sitter-r#189). + +fn(return = TRUE) +fn(return = ) + +# One specific example from r-lib/tree-sitter-r#189 +switch( + x, + return = , +) +``` + +## `return` is just an identifier + +```r +# `return` is a regular symbol in R, not a keyword (r-lib/tree-sitter-r#189). A `return()` +# call is parsed the same as any other call, with `return` as the +# identifier. A bare `return` is also parsed as an identifier. + +function(x) return(x) +return(x) +return +``` + +## Switch + +```r +switch(foo, + x = 1, + "y" = 2, + z = , + 3 +) +``` + +## Calls with NA as argument name [error] + +```r +# Unlike `NULL`, other keywords like `NA` aren't allowed here +fn(NA = ) +``` + +## Not a call, subset, or subset2 [error] + +```r +f +(x) + +foo +[bar] + +foo +[[x]] +``` + +## Not a call, subset, or subset2 due to sequential arguments [error] + +```r +f(x y) +foo[x y] +foo[[x y]] + +f(x, y z) +foo[x, y z] +foo[[x, y z]] + +f(, x y) +foo[, x y] +foo[[, x y]] +``` + +## Call arguments + +```r +fn() +fn(a) +fn(a, b) +fn(...) +fn(a, ..., b) + +fn(a = 1) +fn(a = ) + +fn(a = 1, b = 2) +fn(a = , b = 2) + +fn(... = 1) +fn(... =) + +fn(..1 = 2) +fn(..1 =) + +fn("arg" = 1) +fn("arg" =) + +# Comma tests +fn(,) +fn(,,,) +fn(,,a,,b,,) + +# Comment tests +fn( + # comment +) +fn(, + # comment +,) +fn( + a, # comment1 + b # comment2 +) +fn( + a # comment1 + = # comment2 + 1 # comment3 +) + +{expr}(a = 1) + +# `NULL` argument name (r-lib/tree-sitter-r#164) +fn( + NULL = 1 +) +fn( + NULL = +) +fn( + NULL = , +) + +# A standard call, not a special node (r-lib/tree-sitter-r#197) +return() +``` + +## Side by side arguments [error] + +```r +fn(a b) +fn(a b c) +``` + +## An argument value that starts no expression [error] + +Diagnostic should be about the faulty expression, not a comma + +```r +fn(* 1) +fn(a, * 1) +fn(a = * 1) +fn(,, * 1) +``` + +Same for `[` and `[[` + +```r +x[* 1] +x[[* 1]] +``` + +## Closing delimiter belonging to someone else [error] + +The argument list ends at any closing delimiter, not just its own, so the `}` +survives for its owner rather than being eaten as an argument. Nothing was +written between the `(` and the `}`, so the list is empty rather than holding +one empty argument. + +```r +fn(} +``` + +```r +fn(a} +``` + +## A newline or comment between a name and its `=` + +`parse_argument()` looks one token past the name to find the `=`, and that +lookahead skips trivia to reach it + +```r +f(a + = 1) + +f(a # c + = 1) + +f("a" # c + = 1) +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/call.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/call.snap.md new file mode 100644 index 00000000..d98cc8f6 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/call.snap.md @@ -0,0 +1,3220 @@ + + +## Calls + +```r +f() +f(,) +f(,,) +f(x,) +f(x,,) +f(,x) +f(,,x) +f(x,,y) +f(,,x,,y,,) +f(x=,) +f("x"=,) +f(,y=) + +f(x) +f(1+1) +f(1 ~ 1) +f(x, y) +f(x, y = 2) +f(x = 1 + 1) +f(x, y =) +f(f2(x, y)) + +# Dots as unnamed and named argument +f(...) +f(, ..., 1) +f(... = 1) +f(... = ,) +f(... = ...) + +# `..i` as unnamed and named argument +f(..1) +f(, ..1, 1) +f(..1 = 1) +f(..1 = ,) +f(..1 = ..1) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..1 "f" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@2..3 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@3..5 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@5..6 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@6..7 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@7..8 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@8..10 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@10..11 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@11..12 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@12..13 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@13..14 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@14..16 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@16..17 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@17..18 "x" [] [], + }, + }, + COMMA@18..19 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@19..20 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@20..22 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@22..23 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@23..24 "x" [] [], + }, + }, + COMMA@24..25 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@25..26 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@26..27 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@27..29 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@29..30 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@30..31 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@31..32 "x" [] [], + }, + }, + ], + r_paren_token: R_PAREN@32..33 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@33..35 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@35..36 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@36..37 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@37..38 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@38..39 "x" [] [], + }, + }, + ], + r_paren_token: R_PAREN@39..40 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@40..42 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@42..43 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@43..44 "x" [] [], + }, + }, + COMMA@44..45 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@45..46 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@46..47 "y" [] [], + }, + }, + ], + r_paren_token: R_PAREN@47..48 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@48..50 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@50..51 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@51..52 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@52..53 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@53..54 "x" [] [], + }, + }, + COMMA@54..55 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@55..56 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@56..57 "y" [] [], + }, + }, + COMMA@57..58 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@58..59 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@59..60 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@60..62 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@62..63 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@63..64 "x" [] [], + }, + eq_token: EQUAL@64..65 "=" [] [], + }, + value: missing (optional), + }, + COMMA@65..66 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@66..67 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@67..69 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@69..70 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@70..73 "\"x\"" [] [], + }, + eq_token: EQUAL@73..74 "=" [] [], + }, + value: missing (optional), + }, + COMMA@74..75 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@75..76 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@76..78 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@78..79 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@79..80 "," [] [], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@80..81 "y" [] [], + }, + eq_token: EQUAL@81..82 "=" [] [], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@82..83 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@83..86 "f" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@86..87 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@87..88 "x" [] [], + }, + }, + ], + r_paren_token: R_PAREN@88..89 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@89..91 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@91..92 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@92..93 "1" [] [], + }, + operator: PLUS@93..94 "+" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@94..95 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@95..96 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@96..98 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@98..99 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@99..101 "1" [] [Whitespace(" ")], + }, + operator: TILDE@101..103 "~" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@103..104 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@104..105 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@105..107 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@107..108 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@108..109 "x" [] [], + }, + }, + COMMA@109..111 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@111..112 "y" [] [], + }, + }, + ], + r_paren_token: R_PAREN@112..113 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@113..115 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@115..116 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@116..117 "x" [] [], + }, + }, + COMMA@117..119 "," [] [Whitespace(" ")], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@119..121 "y" [] [Whitespace(" ")], + }, + eq_token: EQUAL@121..123 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@123..124 "2" [] [], + }, + }, + ], + r_paren_token: R_PAREN@124..125 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@125..127 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@127..128 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@128..130 "x" [] [Whitespace(" ")], + }, + eq_token: EQUAL@130..132 "=" [] [Whitespace(" ")], + }, + value: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@132..134 "1" [] [Whitespace(" ")], + }, + operator: PLUS@134..136 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@136..137 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@137..138 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@138..140 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@140..141 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@141..142 "x" [] [], + }, + }, + COMMA@142..144 "," [] [Whitespace(" ")], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@144..146 "y" [] [Whitespace(" ")], + }, + eq_token: EQUAL@146..147 "=" [] [], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@147..148 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@148..150 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@150..151 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RCall { + function: RIdentifier { + name_token: IDENT@151..153 "f2" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@153..154 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@154..155 "x" [] [], + }, + }, + COMMA@155..157 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@157..158 "y" [] [], + }, + }, + ], + r_paren_token: R_PAREN@158..159 ")" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@159..160 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@160..200 "f" [Newline("\n"), Newline("\n"), Comments("# Dots as unnamed and ..."), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@200..201 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDots { + value_token: DOTS@201..204 "..." [] [], + }, + }, + ], + r_paren_token: R_PAREN@204..205 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@205..207 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@207..208 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@208..210 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RDots { + value_token: DOTS@210..213 "..." [] [], + }, + }, + COMMA@213..215 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@215..216 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@216..217 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@217..219 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@219..220 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDots { + value_token: DOTS@220..224 "..." [] [Whitespace(" ")], + }, + eq_token: EQUAL@224..226 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@226..227 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@227..228 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@228..230 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@230..231 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDots { + value_token: DOTS@231..235 "..." [] [Whitespace(" ")], + }, + eq_token: EQUAL@235..237 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + COMMA@237..238 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@238..239 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@239..241 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@241..242 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDots { + value_token: DOTS@242..246 "..." [] [Whitespace(" ")], + }, + eq_token: EQUAL@246..248 "=" [] [Whitespace(" ")], + }, + value: RDots { + value_token: DOTS@248..251 "..." [] [], + }, + }, + ], + r_paren_token: R_PAREN@251..252 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@252..293 "f" [Newline("\n"), Newline("\n"), Comments("# `..i` as unnamed an ..."), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@293..294 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDotDotI { + value_token: DOTDOTI@294..297 "..1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@297..298 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@298..300 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@300..301 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@301..303 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RDotDotI { + value_token: DOTDOTI@303..306 "..1" [] [], + }, + }, + COMMA@306..308 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@308..309 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@309..310 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@310..312 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@312..313 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDotDotI { + value_token: DOTDOTI@313..317 "..1" [] [Whitespace(" ")], + }, + eq_token: EQUAL@317..319 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@319..320 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@320..321 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@321..323 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@323..324 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDotDotI { + value_token: DOTDOTI@324..328 "..1" [] [Whitespace(" ")], + }, + eq_token: EQUAL@328..330 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + COMMA@330..331 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@331..332 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@332..334 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@334..335 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDotDotI { + value_token: DOTDOTI@335..339 "..1" [] [Whitespace(" ")], + }, + eq_token: EQUAL@339..341 "=" [] [Whitespace(" ")], + }, + value: RDotDotI { + value_token: DOTDOTI@341..344 "..1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@344..345 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@345..346 "" [Newline("\n")] [], +} +``` + +## Calls with NULL as argument name + +```r +fn(NULL =) +fn(NULL =,) +fn(NULL = 1) + +# This was likely added to R to support this case for `switch()` +switch(typeof(x), NULL = this, integer = that) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..2 "fn" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@2..3 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RNullExpression { + null_token: NULL_KW@3..8 "NULL" [] [Whitespace(" ")], + }, + eq_token: EQUAL@8..9 "=" [] [], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@9..10 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@10..13 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@13..14 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RNullExpression { + null_token: NULL_KW@14..19 "NULL" [] [Whitespace(" ")], + }, + eq_token: EQUAL@19..20 "=" [] [], + }, + value: missing (optional), + }, + COMMA@20..21 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@21..22 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@22..25 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@25..26 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RNullExpression { + null_token: NULL_KW@26..31 "NULL" [] [Whitespace(" ")], + }, + eq_token: EQUAL@31..33 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@33..34 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@34..35 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@35..108 "switch" [Newline("\n"), Newline("\n"), Comments("# This was likely add ..."), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@108..109 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RCall { + function: RIdentifier { + name_token: IDENT@109..115 "typeof" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@115..116 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@116..117 "x" [] [], + }, + }, + ], + r_paren_token: R_PAREN@117..118 ")" [] [], + }, + }, + }, + COMMA@118..120 "," [] [Whitespace(" ")], + RArgument { + name_clause: RArgumentNameClause { + name: RNullExpression { + null_token: NULL_KW@120..125 "NULL" [] [Whitespace(" ")], + }, + eq_token: EQUAL@125..127 "=" [] [Whitespace(" ")], + }, + value: RIdentifier { + name_token: IDENT@127..131 "this" [] [], + }, + }, + COMMA@131..133 "," [] [Whitespace(" ")], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@133..141 "integer" [] [Whitespace(" ")], + }, + eq_token: EQUAL@141..143 "=" [] [Whitespace(" ")], + }, + value: RIdentifier { + name_token: IDENT@143..147 "that" [] [], + }, + }, + ], + r_paren_token: R_PAREN@147..148 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@148..149 "" [Newline("\n")] [], +} +``` + +## Calls with `return` as argument name + +```r +# `return` is not a reserved word in R (it's not in `?Reserved`), so it is +# allowed as an argument name (r-lib/tree-sitter-r#189). + +fn(return = TRUE) +fn(return = ) + +# One specific example from r-lib/tree-sitter-r#189 +switch( + x, + return = , +) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..135 "fn" [Comments("# `return` is not a r ..."), Newline("\n"), Comments("# allowed as an argum ..."), Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@135..136 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@136..143 "return" [] [Whitespace(" ")], + }, + eq_token: EQUAL@143..145 "=" [] [Whitespace(" ")], + }, + value: RTrueExpression { + true_token: TRUE_KW@145..149 "TRUE" [] [], + }, + }, + ], + r_paren_token: R_PAREN@149..150 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@150..153 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@153..154 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@154..161 "return" [] [Whitespace(" ")], + }, + eq_token: EQUAL@161..163 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@163..164 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@164..224 "switch" [Newline("\n"), Newline("\n"), Comments("# One specific exampl ..."), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@224..225 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@225..229 "x" [Newline("\n"), Whitespace(" ")] [], + }, + }, + COMMA@229..230 "," [] [], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@230..240 "return" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + eq_token: EQUAL@240..242 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + COMMA@242..243 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@243..245 ")" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@245..246 "" [Newline("\n")] [], +} +``` + +## `return` is just an identifier + +```r +# `return` is a regular symbol in R, not a keyword (r-lib/tree-sitter-r#189). A `return()` +# call is parsed the same as any other call, with `return` as the +# identifier. A bare `return` is also parsed as an identifier. + +function(x) return(x) +return(x) +return +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..229 "function" [Comments("# `return` is a regul ..."), Newline("\n"), Comments("# call is parsed the ..."), Newline("\n"), Comments("# identifier. A bare ..."), Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@229..230 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@230..231 "x" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@231..233 ")" [] [Whitespace(" ")], + }, + body: RCall { + function: RIdentifier { + name_token: IDENT@233..239 "return" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@239..240 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@240..241 "x" [] [], + }, + }, + ], + r_paren_token: R_PAREN@241..242 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@242..249 "return" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@249..250 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@250..251 "x" [] [], + }, + }, + ], + r_paren_token: R_PAREN@251..252 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@252..259 "return" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@259..260 "" [Newline("\n")] [], +} +``` + +## Switch + +```r +switch(foo, + x = 1, + "y" = 2, + z = , + 3 +) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..6 "switch" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@6..7 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@7..10 "foo" [] [], + }, + }, + COMMA@10..11 "," [] [], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@11..16 "x" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + eq_token: EQUAL@16..18 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@18..19 "1" [] [], + }, + }, + COMMA@19..20 "," [] [], + RArgument { + name_clause: RArgumentNameClause { + name: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@20..27 "\"y\"" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + eq_token: EQUAL@27..29 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@29..30 "2" [] [], + }, + }, + COMMA@30..31 "," [] [], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@31..36 "z" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + eq_token: EQUAL@36..38 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + COMMA@38..39 "," [] [], + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@39..43 "3" [Newline("\n"), Whitespace(" ")] [], + }, + }, + ], + r_paren_token: R_PAREN@43..45 ")" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@45..46 "" [Newline("\n")] [], +} +``` + +## Calls with NA as argument name [error] + +```r +# Unlike `NULL`, other keywords like `NA` aren't allowed here +fn(NA = ) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..64 "fn" [Comments("# Unlike `NULL`, othe ..."), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@64..65 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RBinaryExpression { + left: RNaExpression { + value: NA_LOGICAL_KW@65..68 "NA" [] [Whitespace(" ")], + }, + operator: EQUAL@68..70 "=" [] [Whitespace(" ")], + right: missing (required), + }, + }, + ], + r_paren_token: R_PAREN@70..71 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@71..72 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ')'. + + 1 │ # Unlike `NULL`, other keywords like `NA` aren't allowed here + > 2 │ fn(NA = ) + │ ^ + 3 │ +``` + +## Not a call, subset, or subset2 [error] + +```r +f +(x) + +foo +[bar] + +foo +[[x]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@0..1 "f" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@1..3 "(" [Newline("\n")] [], + body: RIdentifier { + name_token: IDENT@3..4 "x" [] [], + }, + r_paren_token: R_PAREN@4..5 ")" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@5..10 "foo" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + L_BRACK@10..12 "[" [Newline("\n")] [], + IDENT@12..15 "bar" [] [], + R_BRACK@15..16 "]" [] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@16..21 "foo" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + L_BRACK2@21..24 "[[" [Newline("\n")] [], + IDENT@24..25 "x" [] [], + R_BRACK@25..26 "]" [] [], + R_BRACK@26..27 "]" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@27..28 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:5:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '[bar]'. + + 4 │ foo + > 5 │ [bar] + │ ^^^^^ + 6 │ + 7 │ foo + +test.R:8:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '[[x]]'. + + 7 │ foo + > 8 │ [[x]] + │ ^^^^^ + 9 │ +``` + +## Not a call, subset, or subset2 due to sequential arguments [error] + +```r +f(x y) +foo[x y] +foo[[x y]] + +f(x, y z) +foo[x, y z] +foo[[x, y z]] + +f(, x y) +foo[, x y] +foo[[, x y]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..1 "f" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@2..4 "x" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@4..5 "y" [] [], + }, + }, + ], + r_paren_token: R_PAREN@5..6 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@6..10 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@10..11 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@11..13 "x" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@13..14 "y" [] [], + }, + }, + ], + r_brack_token: R_BRACK@14..15 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@15..19 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@19..21 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@21..23 "x" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@23..24 "y" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@24..26 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@26..29 "f" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@29..30 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@30..31 "x" [] [], + }, + }, + COMMA@31..33 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@33..35 "y" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@35..36 "z" [] [], + }, + }, + ], + r_paren_token: R_PAREN@36..37 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@37..41 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@41..42 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@42..43 "x" [] [], + }, + }, + COMMA@43..45 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@45..47 "y" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@47..48 "z" [] [], + }, + }, + ], + r_brack_token: R_BRACK@48..49 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@49..53 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@53..55 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@55..56 "x" [] [], + }, + }, + COMMA@56..58 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@58..60 "y" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@60..61 "z" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@61..63 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@63..66 "f" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@66..67 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@67..69 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@69..71 "x" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@71..72 "y" [] [], + }, + }, + ], + r_paren_token: R_PAREN@72..73 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@73..77 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@77..78 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@78..80 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@80..82 "x" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@82..83 "y" [] [], + }, + }, + ], + r_brack_token: R_BRACK@83..84 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@84..88 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@88..90 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@90..92 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@92..94 "x" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@94..95 "y" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@95..97 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@97..98 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `y` + + > 1 │ f(x y) + │ ^ + 2 │ foo[x y] + 3 │ foo[[x y]] + + i Remove y + +test.R:2:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `y` + + 1 │ f(x y) + > 2 │ foo[x y] + │ ^ + 3 │ foo[[x y]] + 4 │ + + i Remove y + +test.R:3:8 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `y` + + 1 │ f(x y) + 2 │ foo[x y] + > 3 │ foo[[x y]] + │ ^ + 4 │ + 5 │ f(x, y z) + + i Remove y + +test.R:5:8 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `z` + + 3 │ foo[[x y]] + 4 │ + > 5 │ f(x, y z) + │ ^ + 6 │ foo[x, y z] + 7 │ foo[[x, y z]] + + i Remove z + +test.R:6:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `z` + + 5 │ f(x, y z) + > 6 │ foo[x, y z] + │ ^ + 7 │ foo[[x, y z]] + 8 │ + + i Remove z + +test.R:7:11 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `z` + + 5 │ f(x, y z) + 6 │ foo[x, y z] + > 7 │ foo[[x, y z]] + │ ^ + 8 │ + 9 │ f(, x y) + + i Remove z + +test.R:9:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `y` + + 7 │ foo[[x, y z]] + 8 │ + > 9 │ f(, x y) + │ ^ + 10 │ foo[, x y] + 11 │ foo[[, x y]] + + i Remove y + +test.R:10:9 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `y` + + 9 │ f(, x y) + > 10 │ foo[, x y] + │ ^ + 11 │ foo[[, x y]] + 12 │ + + i Remove y + +test.R:11:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `y` + + 9 │ f(, x y) + 10 │ foo[, x y] + > 11 │ foo[[, x y]] + │ ^ + 12 │ + + i Remove y +``` + +## Call arguments + +```r +fn() +fn(a) +fn(a, b) +fn(...) +fn(a, ..., b) + +fn(a = 1) +fn(a = ) + +fn(a = 1, b = 2) +fn(a = , b = 2) + +fn(... = 1) +fn(... =) + +fn(..1 = 2) +fn(..1 =) + +fn("arg" = 1) +fn("arg" =) + +# Comma tests +fn(,) +fn(,,,) +fn(,,a,,b,,) + +# Comment tests +fn( + # comment +) +fn(, + # comment +,) +fn( + a, # comment1 + b # comment2 +) +fn( + a # comment1 + = # comment2 + 1 # comment3 +) + +{expr}(a = 1) + +# `NULL` argument name (r-lib/tree-sitter-r#164) +fn( + NULL = 1 +) +fn( + NULL = +) +fn( + NULL = , +) + +# A standard call, not a special node (r-lib/tree-sitter-r#197) +return() +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..2 "fn" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@2..3 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@3..4 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@4..7 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@7..8 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@8..9 "a" [] [], + }, + }, + ], + r_paren_token: R_PAREN@9..10 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@10..13 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@13..14 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@14..15 "a" [] [], + }, + }, + COMMA@15..17 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@17..18 "b" [] [], + }, + }, + ], + r_paren_token: R_PAREN@18..19 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@19..22 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@22..23 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDots { + value_token: DOTS@23..26 "..." [] [], + }, + }, + ], + r_paren_token: R_PAREN@26..27 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@27..30 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@30..31 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@31..32 "a" [] [], + }, + }, + COMMA@32..34 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RDots { + value_token: DOTS@34..37 "..." [] [], + }, + }, + COMMA@37..39 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@39..40 "b" [] [], + }, + }, + ], + r_paren_token: R_PAREN@40..41 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@41..45 "fn" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@45..46 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@46..48 "a" [] [Whitespace(" ")], + }, + eq_token: EQUAL@48..50 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@50..51 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@51..52 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@52..55 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@55..56 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@56..58 "a" [] [Whitespace(" ")], + }, + eq_token: EQUAL@58..60 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@60..61 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@61..65 "fn" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@65..66 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@66..68 "a" [] [Whitespace(" ")], + }, + eq_token: EQUAL@68..70 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@70..71 "1" [] [], + }, + }, + COMMA@71..73 "," [] [Whitespace(" ")], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@73..75 "b" [] [Whitespace(" ")], + }, + eq_token: EQUAL@75..77 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@77..78 "2" [] [], + }, + }, + ], + r_paren_token: R_PAREN@78..79 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@79..82 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@82..83 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@83..85 "a" [] [Whitespace(" ")], + }, + eq_token: EQUAL@85..87 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + COMMA@87..89 "," [] [Whitespace(" ")], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@89..91 "b" [] [Whitespace(" ")], + }, + eq_token: EQUAL@91..93 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@93..94 "2" [] [], + }, + }, + ], + r_paren_token: R_PAREN@94..95 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@95..99 "fn" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@99..100 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDots { + value_token: DOTS@100..104 "..." [] [Whitespace(" ")], + }, + eq_token: EQUAL@104..106 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@106..107 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@107..108 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@108..111 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@111..112 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDots { + value_token: DOTS@112..116 "..." [] [Whitespace(" ")], + }, + eq_token: EQUAL@116..117 "=" [] [], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@117..118 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@118..122 "fn" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@122..123 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDotDotI { + value_token: DOTDOTI@123..127 "..1" [] [Whitespace(" ")], + }, + eq_token: EQUAL@127..129 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@129..130 "2" [] [], + }, + }, + ], + r_paren_token: R_PAREN@130..131 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@131..134 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@134..135 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RDotDotI { + value_token: DOTDOTI@135..139 "..1" [] [Whitespace(" ")], + }, + eq_token: EQUAL@139..140 "=" [] [], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@140..141 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@141..145 "fn" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@145..146 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@146..152 "\"arg\"" [] [Whitespace(" ")], + }, + eq_token: EQUAL@152..154 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@154..155 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@155..156 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@156..159 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@159..160 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@160..166 "\"arg\"" [] [Whitespace(" ")], + }, + eq_token: EQUAL@166..167 "=" [] [], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@167..168 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@168..186 "fn" [Newline("\n"), Newline("\n"), Comments("# Comma tests"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@186..187 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@187..188 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@188..189 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@189..192 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@192..193 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@193..194 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@194..195 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@195..196 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@196..197 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@197..200 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@200..201 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@201..202 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@202..203 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@203..204 "a" [] [], + }, + }, + COMMA@204..205 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@205..206 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@206..207 "b" [] [], + }, + }, + COMMA@207..208 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@208..209 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@209..210 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@210..230 "fn" [Newline("\n"), Newline("\n"), Comments("# Comment tests"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@230..231 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@231..245 ")" [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@245..248 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@248..249 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@249..250 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@250..264 "," [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@264..265 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@265..268 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@268..269 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@269..273 "a" [Newline("\n"), Whitespace(" ")] [], + }, + }, + COMMA@273..285 "," [] [Whitespace(" "), Comments("# comment1")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@285..300 "b" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")], + }, + }, + ], + r_paren_token: R_PAREN@300..302 ")" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@302..305 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@305..306 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@306..321 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment1")], + }, + eq_token: EQUAL@321..336 "=" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@336..351 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")], + }, + }, + ], + r_paren_token: R_PAREN@351..353 ")" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RBracedStatements { + l_curly_token: L_CURLY@353..356 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@356..360 "expr" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@360..361 "}" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@361..362 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@362..364 "a" [] [Whitespace(" ")], + }, + eq_token: EQUAL@364..366 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@366..367 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@367..368 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@368..421 "fn" [Newline("\n"), Newline("\n"), Comments("# `NULL` argument nam ..."), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@421..422 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RNullExpression { + null_token: NULL_KW@422..430 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + eq_token: EQUAL@430..432 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@432..433 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@433..435 ")" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@435..438 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@438..439 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RNullExpression { + null_token: NULL_KW@439..447 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + eq_token: EQUAL@447..449 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@449..451 ")" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@451..454 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@454..455 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RNullExpression { + null_token: NULL_KW@455..463 "NULL" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + eq_token: EQUAL@463..465 "=" [] [Whitespace(" ")], + }, + value: missing (optional), + }, + COMMA@465..466 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_paren_token: R_PAREN@466..468 ")" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@468..540 "return" [Newline("\n"), Newline("\n"), Comments("# A standard call, no ..."), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@540..541 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@541..542 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@542..543 "" [Newline("\n")] [], +} +``` + +## Side by side arguments [error] + +```r +fn(a b) +fn(a b c) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..2 "fn" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@2..3 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@3..5 "a" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@5..6 "b" [] [], + }, + }, + ], + r_paren_token: R_PAREN@6..7 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@7..10 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@10..11 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@11..13 "a" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@13..15 "b" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@15..16 "c" [] [], + }, + }, + ], + r_paren_token: R_PAREN@16..17 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@17..18 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `b` + + > 1 │ fn(a b) + │ ^ + 2 │ fn(a b c) + 3 │ + + i Remove b + +test.R:2:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `b` + + 1 │ fn(a b) + > 2 │ fn(a b c) + │ ^ + 3 │ + + i Remove b + +test.R:2:8 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `c` + + 1 │ fn(a b) + > 2 │ fn(a b c) + │ ^ + 3 │ + + i Remove c +``` + +## An argument value that starts no expression [error] + +Diagnostic should be about the faulty expression, not a comma + +```r +fn(* 1) +fn(a, * 1) +fn(a = * 1) +fn(,, * 1) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..2 "fn" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@2..3 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RBogusExpression { + items: [ + MULTIPLY@3..5 "*" [] [Whitespace(" ")], + R_DOUBLE_LITERAL@5..6 "1" [] [], + ], + }, + }, + ], + r_paren_token: R_PAREN@6..7 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@7..10 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@10..11 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@11..12 "a" [] [], + }, + }, + COMMA@12..14 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RBogusExpression { + items: [ + MULTIPLY@14..16 "*" [] [Whitespace(" ")], + R_DOUBLE_LITERAL@16..17 "1" [] [], + ], + }, + }, + ], + r_paren_token: R_PAREN@17..18 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@18..21 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@21..22 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@22..24 "a" [] [Whitespace(" ")], + }, + eq_token: EQUAL@24..26 "=" [] [Whitespace(" ")], + }, + value: RBogusExpression { + items: [ + MULTIPLY@26..28 "*" [] [Whitespace(" ")], + R_DOUBLE_LITERAL@28..29 "1" [] [], + ], + }, + }, + ], + r_paren_token: R_PAREN@29..30 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@30..33 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@33..34 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@34..35 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@35..37 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RBogusExpression { + items: [ + MULTIPLY@37..39 "*" [] [Whitespace(" ")], + R_DOUBLE_LITERAL@39..40 "1" [] [], + ], + }, + }, + ], + r_paren_token: R_PAREN@40..41 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@41..42 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '* 1'. + + > 1 │ fn(* 1) + │ ^^^ + 2 │ fn(a, * 1) + 3 │ fn(a = * 1) + +test.R:2:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '* 1'. + + 1 │ fn(* 1) + > 2 │ fn(a, * 1) + │ ^^^ + 3 │ fn(a = * 1) + 4 │ fn(,, * 1) + +test.R:3:8 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '* 1'. + + 1 │ fn(* 1) + 2 │ fn(a, * 1) + > 3 │ fn(a = * 1) + │ ^^^ + 4 │ fn(,, * 1) + 5 │ + +test.R:4:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '* 1'. + + 2 │ fn(a, * 1) + 3 │ fn(a = * 1) + > 4 │ fn(,, * 1) + │ ^^^ + 5 │ +``` + +Same for `[` and `[[` + +```r +x[* 1] +x[[* 1]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@1..2 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RBogusExpression { + items: [ + MULTIPLY@2..4 "*" [] [Whitespace(" ")], + R_DOUBLE_LITERAL@4..5 "1" [] [], + ], + }, + }, + ], + r_brack_token: R_BRACK@5..6 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@6..8 "x" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@8..10 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RBogusExpression { + items: [ + MULTIPLY@10..12 "*" [] [Whitespace(" ")], + R_DOUBLE_LITERAL@12..13 "1" [] [], + ], + }, + }, + ], + r_brack2_token: R_BRACK2@13..15 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@15..16 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '* 1'. + + > 1 │ x[* 1] + │ ^^^ + 2 │ x[[* 1]] + 3 │ + +test.R:2:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '* 1'. + + 1 │ x[* 1] + > 2 │ x[[* 1]] + │ ^^^ + 3 │ +``` + +## Closing delimiter belonging to someone else [error] + +The argument list ends at any closing delimiter, not just its own, so the `}` +survives for its owner rather than being eaten as an argument. Nothing was +written between the `(` and the `}`, so the list is empty rather than holding +one empty argument. + +```r +fn(} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..2 "fn" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@2..3 "(" [] [], + items: RArgumentList [], + r_paren_token: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_CURLY@3..4 "}" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@4..5 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `}` + + > 1 │ fn(} + │ ^ + 2 │ + + i Remove } +``` + +```r +fn(a} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..2 "fn" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@2..3 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@3..4 "a" [] [], + }, + }, + ], + r_paren_token: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_CURLY@4..5 "}" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@5..6 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `}` + + > 1 │ fn(a} + │ ^ + 2 │ + + i Remove } +``` + +## A newline or comment between a name and its `=` + +`parse_argument()` looks one token past the name to find the `=`, and that +lookahead skips trivia to reach it + +```r +f(a + = 1) + +f(a # c + = 1) + +f("a" # c + = 1) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..1 "f" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@2..3 "a" [] [], + }, + eq_token: EQUAL@3..8 "=" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@8..9 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@9..10 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@10..13 "f" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@13..14 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@14..19 "a" [] [Whitespace(" "), Comments("# c")], + }, + eq_token: EQUAL@19..24 "=" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@24..25 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@25..26 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@26..29 "f" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@29..30 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@30..37 "\"a\"" [] [Whitespace(" "), Comments("# c")], + }, + eq_token: EQUAL@37..42 "=" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@42..43 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@43..44 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@44..45 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/colon.md b/crates/air_r_parser_tests/tests/corpus/expressions/colon.md new file mode 100644 index 00000000..b6e94518 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/colon.md @@ -0,0 +1,6 @@ +## Colon + +```r +1:2 +(1 + 1):-5 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/colon.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/colon.snap.md new file mode 100644 index 00000000..c8bccaf3 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/colon.snap.md @@ -0,0 +1,54 @@ + + +## Colon + +```r +1:2 +(1 + 1):-5 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], + }, + operator: COLON@1..2 ":" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@2..3 "2" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RParenthesizedExpression { + l_paren_token: L_PAREN@3..5 "(" [Newline("\n")] [], + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..7 "1" [] [Whitespace(" ")], + }, + operator: PLUS@7..9 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@9..10 "1" [] [], + }, + }, + r_paren_token: R_PAREN@10..11 ")" [] [], + }, + operator: COLON@11..12 ":" [] [], + right: RUnaryExpression { + operator: MINUS@12..13 "-" [] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@13..14 "5" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@14..15 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/extract.md b/crates/air_r_parser_tests/tests/corpus/expressions/extract.md new file mode 100644 index 00000000..9ed11613 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/extract.md @@ -0,0 +1,134 @@ +## Dollar + +```r +foo$bar +foo$bar$baz +foo$bar@baz +foo$bar() +foo$"bar" +foo$bar()$baz[[1]]$bam +foo$... +foo$..1 +``` + +## Slot + +```r +foo@bar +foo@bar$baz +foo@bar() +foo@"bar" +foo@... +foo@..1 +``` + +## Dollar, at, namespace, namespace internal with expression RHS [error] + +```r +# The RHS of these nodes must be a string/identifier, so we nicely get a true +# node here alongside the braces. Even if that's not parsable R code, it's +# useful for completions and highlighting. +foo${bar} +foo@{bar} +foo::{bar} +foo:::{bar} +``` + +## Dollar no RHS [error] + +```r +foo$ +``` + +## Slot no RHS [error] + +```r +foo@ +``` + +## Dollar, at, namespace, namespace internal with `if` RHS [error] + +```r +# The RHS's of these nodes are restricted to strings and identifiers, so the RHS +# is left empty and the `if` goes on to be its own statement, which then errors +# because it doesn't have a `(`. This is actually pretty decent behavior. +foo$if +foo@if +foo::if +foo:::if +``` + +## Dollar, at, namespace, namespace internal with newline before operator [error] + +```r +# Newlines are not allowed before the operator, so none of these are valid +x +$y + +x +@y + +x +::y + +x +:::y +``` + +## Extract operators + +```r +a$b +a@b + +a$`b` +a@`b` + +a$"b" +a@"b" + +a()$b +a()@b + +a$b$c +a@b@c + +a$b() +a@b() + +a$... +a@... + +a$..1 +a@..1 + +a$..10 +a@..10 +``` + +## Extract with a literal RHS [error] + +The RHS slot is left missing rather than swallowing the invalid right-hand side. + +```r +a$1 +a$NA +a$NULL +a$TRUE +a$(b) +a${ b } + +a@1 +a@NA +a@NULL +a@TRUE +a@(b) +a@{ b } +``` + +## Dollar tolerates a newline + +```r +df$ + x +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/extract.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/extract.snap.md new file mode 100644 index 00000000..c7bba777 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/extract.snap.md @@ -0,0 +1,1713 @@ + + +## Dollar + +```r +foo$bar +foo$bar$baz +foo$bar@baz +foo$bar() +foo$"bar" +foo$bar()$baz[[1]]$bam +foo$... +foo$..1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + operator: DOLLAR@3..4 "$" [] [], + right: RIdentifier { + name_token: IDENT@4..7 "bar" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RExtractExpression { + left: RIdentifier { + name_token: IDENT@7..11 "foo" [Newline("\n")] [], + }, + operator: DOLLAR@11..12 "$" [] [], + right: RIdentifier { + name_token: IDENT@12..15 "bar" [] [], + }, + }, + operator: DOLLAR@15..16 "$" [] [], + right: RIdentifier { + name_token: IDENT@16..19 "baz" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RExtractExpression { + left: RIdentifier { + name_token: IDENT@19..23 "foo" [Newline("\n")] [], + }, + operator: DOLLAR@23..24 "$" [] [], + right: RIdentifier { + name_token: IDENT@24..27 "bar" [] [], + }, + }, + operator: AT@27..28 "@" [] [], + right: RIdentifier { + name_token: IDENT@28..31 "baz" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RExtractExpression { + left: RIdentifier { + name_token: IDENT@31..35 "foo" [Newline("\n")] [], + }, + operator: DOLLAR@35..36 "$" [] [], + right: RIdentifier { + name_token: IDENT@36..39 "bar" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@39..40 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@40..41 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@41..45 "foo" [Newline("\n")] [], + }, + operator: DOLLAR@45..46 "$" [] [], + right: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@46..51 "\"bar\"" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RSubset2 { + function: RExtractExpression { + left: RCall { + function: RExtractExpression { + left: RIdentifier { + name_token: IDENT@51..55 "foo" [Newline("\n")] [], + }, + operator: DOLLAR@55..56 "$" [] [], + right: RIdentifier { + name_token: IDENT@56..59 "bar" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@59..60 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@60..61 ")" [] [], + }, + }, + operator: DOLLAR@61..62 "$" [] [], + right: RIdentifier { + name_token: IDENT@62..65 "baz" [] [], + }, + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@65..67 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@67..68 "1" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@68..70 "]]" [] [], + }, + }, + operator: DOLLAR@70..71 "$" [] [], + right: RIdentifier { + name_token: IDENT@71..74 "bam" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@74..78 "foo" [Newline("\n")] [], + }, + operator: DOLLAR@78..79 "$" [] [], + right: RDots { + value_token: DOTS@79..82 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@82..86 "foo" [Newline("\n")] [], + }, + operator: DOLLAR@86..87 "$" [] [], + right: RDotDotI { + value_token: DOTDOTI@87..90 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@90..91 "" [Newline("\n")] [], +} +``` + +## Slot + +```r +foo@bar +foo@bar$baz +foo@bar() +foo@"bar" +foo@... +foo@..1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + operator: AT@3..4 "@" [] [], + right: RIdentifier { + name_token: IDENT@4..7 "bar" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RExtractExpression { + left: RIdentifier { + name_token: IDENT@7..11 "foo" [Newline("\n")] [], + }, + operator: AT@11..12 "@" [] [], + right: RIdentifier { + name_token: IDENT@12..15 "bar" [] [], + }, + }, + operator: DOLLAR@15..16 "$" [] [], + right: RIdentifier { + name_token: IDENT@16..19 "baz" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RExtractExpression { + left: RIdentifier { + name_token: IDENT@19..23 "foo" [Newline("\n")] [], + }, + operator: AT@23..24 "@" [] [], + right: RIdentifier { + name_token: IDENT@24..27 "bar" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@27..28 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@28..29 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@29..33 "foo" [Newline("\n")] [], + }, + operator: AT@33..34 "@" [] [], + right: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@34..39 "\"bar\"" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@39..43 "foo" [Newline("\n")] [], + }, + operator: AT@43..44 "@" [] [], + right: RDots { + value_token: DOTS@44..47 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@47..51 "foo" [Newline("\n")] [], + }, + operator: AT@51..52 "@" [] [], + right: RDotDotI { + value_token: DOTDOTI@52..55 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@55..56 "" [Newline("\n")] [], +} +``` + +## Dollar, at, namespace, namespace internal with expression RHS [error] + +```r +# The RHS of these nodes must be a string/identifier, so we nicely get a true +# node here alongside the braces. Even if that's not parsable R code, it's +# useful for completions and highlighting. +foo${bar} +foo@{bar} +foo::{bar} +foo:::{bar} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..199 "foo" [Comments("# The RHS of these no ..."), Newline("\n"), Comments("# node here alongside ..."), Newline("\n"), Comments("# useful for completi ..."), Newline("\n")] [], + }, + operator: DOLLAR@199..200 "$" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@200..201 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@201..204 "bar" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@204..205 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@205..209 "foo" [Newline("\n")] [], + }, + operator: AT@209..210 "@" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@210..211 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@211..214 "bar" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@214..215 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@215..219 "foo" [Newline("\n")] [], + }, + operator: COLON2@219..221 "::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@221..222 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@222..225 "bar" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@225..226 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@226..230 "foo" [Newline("\n")] [], + }, + operator: COLON3@230..233 ":::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@233..234 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@234..237 "bar" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@237..238 "}" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@238..239 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:4:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 2 │ # node here alongside the braces. Even if that's not parsable R code, it's + 3 │ # useful for completions and highlighting. + > 4 │ foo${bar} + │ ^ + 5 │ foo@{bar} + 6 │ foo::{bar} + + i `$` requires a name on the right. + + 2 │ # node here alongside the braces. Even if that's not parsable R code, it's + 3 │ # useful for completions and highlighting. + > 4 │ foo${bar} + │ ^ + 5 │ foo@{bar} + 6 │ foo::{bar} + +test.R:5:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 3 │ # useful for completions and highlighting. + 4 │ foo${bar} + > 5 │ foo@{bar} + │ ^ + 6 │ foo::{bar} + 7 │ foo:::{bar} + + i `@` requires a name on the right. + + 3 │ # useful for completions and highlighting. + 4 │ foo${bar} + > 5 │ foo@{bar} + │ ^ + 6 │ foo::{bar} + 7 │ foo:::{bar} + +test.R:6:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 4 │ foo${bar} + 5 │ foo@{bar} + > 6 │ foo::{bar} + │ ^ + 7 │ foo:::{bar} + 8 │ + + i `::` requires a name on the right. + + 4 │ foo${bar} + 5 │ foo@{bar} + > 6 │ foo::{bar} + │ ^^ + 7 │ foo:::{bar} + 8 │ + +test.R:7:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 5 │ foo@{bar} + 6 │ foo::{bar} + > 7 │ foo:::{bar} + │ ^ + 8 │ + + i `:::` requires a name on the right. + + 5 │ foo@{bar} + 6 │ foo::{bar} + > 7 │ foo:::{bar} + │ ^^^ + 8 │ +``` + +## Dollar no RHS [error] + +```r +foo$ +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + operator: DOLLAR@3..4 "$" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@4..5 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 1 │ foo$ + > 2 │ + │ + + i `$` requires a name on the right. + + > 1 │ foo$ + │ ^ + 2 │ +``` + +## Slot no RHS [error] + +```r +foo@ +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + operator: AT@3..4 "@" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@4..5 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 1 │ foo@ + > 2 │ + │ + + i `@` requires a name on the right. + + > 1 │ foo@ + │ ^ + 2 │ +``` + +## Dollar, at, namespace, namespace internal with `if` RHS [error] + +```r +# The RHS's of these nodes are restricted to strings and identifiers, so the RHS +# is left empty and the `if` goes on to be its own statement, which then errors +# because it doesn't have a `(`. This is actually pretty decent behavior. +foo$if +foo@if +foo::if +foo:::if +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..238 "foo" [Comments("# The RHS's of these ..."), Newline("\n"), Comments("# is left empty and t ..."), Newline("\n"), Comments("# because it doesn't ..."), Newline("\n")] [], + }, + operator: DOLLAR@238..239 "$" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@239..241 "if" [] [], + l_paren_token: missing (required), + condition: RExtractExpression { + left: RIdentifier { + name_token: IDENT@241..245 "foo" [Newline("\n")] [], + }, + operator: AT@245..246 "@" [] [], + right: missing (required), + }, + r_paren_token: missing (required), + consequence: RIfStatement { + if_token: IF_KW@246..248 "if" [] [], + l_paren_token: missing (required), + condition: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@248..252 "foo" [Newline("\n")] [], + }, + operator: COLON2@252..254 "::" [] [], + right: missing (required), + }, + r_paren_token: missing (required), + consequence: RIfStatement { + if_token: IF_KW@254..256 "if" [] [], + l_paren_token: missing (required), + condition: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@256..260 "foo" [Newline("\n")] [], + }, + operator: COLON3@260..263 ":::" [] [], + right: missing (required), + }, + r_paren_token: missing (required), + consequence: RIfStatement { + if_token: IF_KW@263..265 "if" [] [], + l_paren_token: missing (required), + condition: missing (required), + r_paren_token: missing (required), + consequence: missing (required), + else_clause: missing (optional), + }, + else_clause: missing (optional), + }, + else_clause: missing (optional), + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@265..266 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:4:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 2 │ # is left empty and the `if` goes on to be its own statement, which then errors + 3 │ # because it doesn't have a `(`. This is actually pretty decent behavior. + > 4 │ foo$if + │ ^^ + 5 │ foo@if + 6 │ foo::if + + i `$` requires a name on the right. + + 2 │ # is left empty and the `if` goes on to be its own statement, which then errors + 3 │ # because it doesn't have a `(`. This is actually pretty decent behavior. + > 4 │ foo$if + │ ^ + 5 │ foo@if + 6 │ foo::if + +test.R:5:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `(` but instead found `foo` + + 3 │ # because it doesn't have a `(`. This is actually pretty decent behavior. + 4 │ foo$if + > 5 │ foo@if + │ ^^^ + 6 │ foo::if + 7 │ foo:::if + + i Remove foo + +test.R:5:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 3 │ # because it doesn't have a `(`. This is actually pretty decent behavior. + 4 │ foo$if + > 5 │ foo@if + │ ^^ + 6 │ foo::if + 7 │ foo:::if + + i `@` requires a name on the right. + + 3 │ # because it doesn't have a `(`. This is actually pretty decent behavior. + 4 │ foo$if + > 5 │ foo@if + │ ^ + 6 │ foo::if + 7 │ foo:::if + +test.R:6:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `(` but instead found `foo` + + 4 │ foo$if + 5 │ foo@if + > 6 │ foo::if + │ ^^^ + 7 │ foo:::if + 8 │ + + i Remove foo + +test.R:6:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 4 │ foo$if + 5 │ foo@if + > 6 │ foo::if + │ ^^ + 7 │ foo:::if + 8 │ + + i `::` requires a name on the right. + + 4 │ foo$if + 5 │ foo@if + > 6 │ foo::if + │ ^^ + 7 │ foo:::if + 8 │ + +test.R:7:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `(` but instead found `foo` + + 5 │ foo@if + 6 │ foo::if + > 7 │ foo:::if + │ ^^^ + 8 │ + + i Remove foo + +test.R:7:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 5 │ foo@if + 6 │ foo::if + > 7 │ foo:::if + │ ^^ + 8 │ + + i `:::` requires a name on the right. + + 5 │ foo@if + 6 │ foo::if + > 7 │ foo:::if + │ ^^^ + 8 │ + +test.R:8:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `(` but instead the file ends + + 6 │ foo::if + 7 │ foo:::if + > 8 │ + │ + + i the file ends here + + 6 │ foo::if + 7 │ foo:::if + > 8 │ + │ +``` + +## Dollar, at, namespace, namespace internal with newline before operator [error] + +```r +# Newlines are not allowed before the operator, so none of these are valid +x +$y + +x +@y + +x +::y + +x +:::y +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@0..76 "x" [Comments("# Newlines are not al ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + DOLLAR@76..78 "$" [Newline("\n")] [], + IDENT@78..79 "y" [] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@79..82 "x" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + AT@82..84 "@" [Newline("\n")] [], + IDENT@84..85 "y" [] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@85..88 "x" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + COLON2@88..91 "::" [Newline("\n")] [], + IDENT@91..92 "y" [] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@92..95 "x" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + COLON3@95..99 ":::" [Newline("\n")] [], + IDENT@99..100 "y" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@100..101 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:3:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '$y'. + + 1 │ # Newlines are not allowed before the operator, so none of these are valid + 2 │ x + > 3 │ $y + │ ^^ + 4 │ + 5 │ x + +test.R:6:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '@y'. + + 5 │ x + > 6 │ @y + │ ^^ + 7 │ + 8 │ x + +test.R:9:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '::y'. + + 8 │ x + > 9 │ ::y + │ ^^^ + 10 │ + 11 │ x + +test.R:12:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ':::y'. + + 11 │ x + > 12 │ :::y + │ ^^^^ + 13 │ +``` + +## Extract operators + +```r +a$b +a@b + +a$`b` +a@`b` + +a$"b" +a@"b" + +a()$b +a()@b + +a$b$c +a@b@c + +a$b() +a@b() + +a$... +a@... + +a$..1 +a@..1 + +a$..10 +a@..10 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + operator: DOLLAR@1..2 "$" [] [], + right: RIdentifier { + name_token: IDENT@2..3 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@3..5 "a" [Newline("\n")] [], + }, + operator: AT@5..6 "@" [] [], + right: RIdentifier { + name_token: IDENT@6..7 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@7..10 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: DOLLAR@10..11 "$" [] [], + right: RIdentifier { + name_token: IDENT@11..14 "`b`" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@14..16 "a" [Newline("\n")] [], + }, + operator: AT@16..17 "@" [] [], + right: RIdentifier { + name_token: IDENT@17..20 "`b`" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@20..23 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: DOLLAR@23..24 "$" [] [], + right: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@24..27 "\"b\"" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@27..29 "a" [Newline("\n")] [], + }, + operator: AT@29..30 "@" [] [], + right: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@30..33 "\"b\"" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RCall { + function: RIdentifier { + name_token: IDENT@33..36 "a" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@36..37 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@37..38 ")" [] [], + }, + }, + operator: DOLLAR@38..39 "$" [] [], + right: RIdentifier { + name_token: IDENT@39..40 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RCall { + function: RIdentifier { + name_token: IDENT@40..42 "a" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@42..43 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@43..44 ")" [] [], + }, + }, + operator: AT@44..45 "@" [] [], + right: RIdentifier { + name_token: IDENT@45..46 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RExtractExpression { + left: RIdentifier { + name_token: IDENT@46..49 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: DOLLAR@49..50 "$" [] [], + right: RIdentifier { + name_token: IDENT@50..51 "b" [] [], + }, + }, + operator: DOLLAR@51..52 "$" [] [], + right: RIdentifier { + name_token: IDENT@52..53 "c" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RExtractExpression { + left: RIdentifier { + name_token: IDENT@53..55 "a" [Newline("\n")] [], + }, + operator: AT@55..56 "@" [] [], + right: RIdentifier { + name_token: IDENT@56..57 "b" [] [], + }, + }, + operator: AT@57..58 "@" [] [], + right: RIdentifier { + name_token: IDENT@58..59 "c" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RExtractExpression { + left: RIdentifier { + name_token: IDENT@59..62 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: DOLLAR@62..63 "$" [] [], + right: RIdentifier { + name_token: IDENT@63..64 "b" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@64..65 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@65..66 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RExtractExpression { + left: RIdentifier { + name_token: IDENT@66..68 "a" [Newline("\n")] [], + }, + operator: AT@68..69 "@" [] [], + right: RIdentifier { + name_token: IDENT@69..70 "b" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@70..71 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@71..72 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@72..75 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: DOLLAR@75..76 "$" [] [], + right: RDots { + value_token: DOTS@76..79 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@79..81 "a" [Newline("\n")] [], + }, + operator: AT@81..82 "@" [] [], + right: RDots { + value_token: DOTS@82..85 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@85..88 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: DOLLAR@88..89 "$" [] [], + right: RDotDotI { + value_token: DOTDOTI@89..92 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@92..94 "a" [Newline("\n")] [], + }, + operator: AT@94..95 "@" [] [], + right: RDotDotI { + value_token: DOTDOTI@95..98 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@98..101 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: DOLLAR@101..102 "$" [] [], + right: RDotDotI { + value_token: DOTDOTI@102..106 "..10" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@106..108 "a" [Newline("\n")] [], + }, + operator: AT@108..109 "@" [] [], + right: RDotDotI { + value_token: DOTDOTI@109..113 "..10" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@113..114 "" [Newline("\n")] [], +} +``` + +## Extract with a literal RHS [error] + +The RHS slot is left missing rather than swallowing the invalid right-hand side. + +```r +a$1 +a$NA +a$NULL +a$TRUE +a$(b) +a${ b } + +a@1 +a@NA +a@NULL +a@TRUE +a@(b) +a@{ b } +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + operator: DOLLAR@1..2 "$" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@2..3 "1" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@3..5 "a" [Newline("\n")] [], + }, + operator: DOLLAR@5..6 "$" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNaExpression { + value: NA_LOGICAL_KW@6..8 "NA" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@8..10 "a" [Newline("\n")] [], + }, + operator: DOLLAR@10..11 "$" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNullExpression { + null_token: NULL_KW@11..15 "NULL" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@15..17 "a" [Newline("\n")] [], + }, + operator: DOLLAR@17..18 "$" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RTrueExpression { + true_token: TRUE_KW@18..22 "TRUE" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RExtractExpression { + left: RIdentifier { + name_token: IDENT@22..24 "a" [Newline("\n")] [], + }, + operator: DOLLAR@24..25 "$" [] [], + right: missing (required), + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@25..26 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@26..27 "b" [] [], + }, + }, + ], + r_paren_token: R_PAREN@27..28 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@28..30 "a" [Newline("\n")] [], + }, + operator: DOLLAR@30..31 "$" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@31..33 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@33..35 "b" [] [Whitespace(" ")], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@35..36 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@36..39 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: AT@39..40 "@" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@40..41 "1" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@41..43 "a" [Newline("\n")] [], + }, + operator: AT@43..44 "@" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNaExpression { + value: NA_LOGICAL_KW@44..46 "NA" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@46..48 "a" [Newline("\n")] [], + }, + operator: AT@48..49 "@" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNullExpression { + null_token: NULL_KW@49..53 "NULL" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@53..55 "a" [Newline("\n")] [], + }, + operator: AT@55..56 "@" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RTrueExpression { + true_token: TRUE_KW@56..60 "TRUE" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RExtractExpression { + left: RIdentifier { + name_token: IDENT@60..62 "a" [Newline("\n")] [], + }, + operator: AT@62..63 "@" [] [], + right: missing (required), + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@63..64 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@64..65 "b" [] [], + }, + }, + ], + r_paren_token: R_PAREN@65..66 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@66..68 "a" [Newline("\n")] [], + }, + operator: AT@68..69 "@" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@69..71 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@71..73 "b" [] [Whitespace(" ")], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@73..74 "}" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@74..75 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + > 1 │ a$1 + │ ^ + 2 │ a$NA + 3 │ a$NULL + + i `$` requires a name on the right. + + > 1 │ a$1 + │ ^ + 2 │ a$NA + 3 │ a$NULL + +test.R:2:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 1 │ a$1 + > 2 │ a$NA + │ ^^ + 3 │ a$NULL + 4 │ a$TRUE + + i `$` requires a name on the right. + + 1 │ a$1 + > 2 │ a$NA + │ ^ + 3 │ a$NULL + 4 │ a$TRUE + +test.R:3:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 1 │ a$1 + 2 │ a$NA + > 3 │ a$NULL + │ ^^^^ + 4 │ a$TRUE + 5 │ a$(b) + + i `$` requires a name on the right. + + 1 │ a$1 + 2 │ a$NA + > 3 │ a$NULL + │ ^ + 4 │ a$TRUE + 5 │ a$(b) + +test.R:4:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 2 │ a$NA + 3 │ a$NULL + > 4 │ a$TRUE + │ ^^^^ + 5 │ a$(b) + 6 │ a${ b } + + i `$` requires a name on the right. + + 2 │ a$NA + 3 │ a$NULL + > 4 │ a$TRUE + │ ^ + 5 │ a$(b) + 6 │ a${ b } + +test.R:5:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 3 │ a$NULL + 4 │ a$TRUE + > 5 │ a$(b) + │ ^ + 6 │ a${ b } + 7 │ + + i `$` requires a name on the right. + + 3 │ a$NULL + 4 │ a$TRUE + > 5 │ a$(b) + │ ^ + 6 │ a${ b } + 7 │ + +test.R:6:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 4 │ a$TRUE + 5 │ a$(b) + > 6 │ a${ b } + │ ^ + 7 │ + 8 │ a@1 + + i `$` requires a name on the right. + + 4 │ a$TRUE + 5 │ a$(b) + > 6 │ a${ b } + │ ^ + 7 │ + 8 │ a@1 + +test.R:8:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 6 │ a${ b } + 7 │ + > 8 │ a@1 + │ ^ + 9 │ a@NA + 10 │ a@NULL + + i `@` requires a name on the right. + + 6 │ a${ b } + 7 │ + > 8 │ a@1 + │ ^ + 9 │ a@NA + 10 │ a@NULL + +test.R:9:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 8 │ a@1 + > 9 │ a@NA + │ ^^ + 10 │ a@NULL + 11 │ a@TRUE + + i `@` requires a name on the right. + + 8 │ a@1 + > 9 │ a@NA + │ ^ + 10 │ a@NULL + 11 │ a@TRUE + +test.R:10:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 8 │ a@1 + 9 │ a@NA + > 10 │ a@NULL + │ ^^^^ + 11 │ a@TRUE + 12 │ a@(b) + + i `@` requires a name on the right. + + 8 │ a@1 + 9 │ a@NA + > 10 │ a@NULL + │ ^ + 11 │ a@TRUE + 12 │ a@(b) + +test.R:11:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 9 │ a@NA + 10 │ a@NULL + > 11 │ a@TRUE + │ ^^^^ + 12 │ a@(b) + 13 │ a@{ b } + + i `@` requires a name on the right. + + 9 │ a@NA + 10 │ a@NULL + > 11 │ a@TRUE + │ ^ + 12 │ a@(b) + 13 │ a@{ b } + +test.R:12:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 10 │ a@NULL + 11 │ a@TRUE + > 12 │ a@(b) + │ ^ + 13 │ a@{ b } + 14 │ + + i `@` requires a name on the right. + + 10 │ a@NULL + 11 │ a@TRUE + > 12 │ a@(b) + │ ^ + 13 │ a@{ b } + 14 │ + +test.R:13:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 11 │ a@TRUE + 12 │ a@(b) + > 13 │ a@{ b } + │ ^ + 14 │ + + i `@` requires a name on the right. + + 11 │ a@TRUE + 12 │ a@(b) + > 13 │ a@{ b } + │ ^ + 14 │ +``` + +## Dollar tolerates a newline + +```r +df$ + x +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..2 "df" [] [], + }, + operator: DOLLAR@2..3 "$" [] [], + right: RIdentifier { + name_token: IDENT@3..7 "x" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/formula.md b/crates/air_r_parser_tests/tests/corpus/expressions/formula.md new file mode 100644 index 00000000..6375013b --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/formula.md @@ -0,0 +1,6 @@ +## Formulas + +```r +~x +y~x +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/formula.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/formula.snap.md new file mode 100644 index 00000000..c5fbc3b1 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/formula.snap.md @@ -0,0 +1,38 @@ + + +## Formulas + +```r +~x +y~x +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RUnaryExpression { + operator: TILDE@0..1 "~" [] [], + argument: RIdentifier { + name_token: IDENT@1..2 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@2..4 "y" [Newline("\n")] [], + }, + operator: TILDE@4..5 "~" [] [], + right: RIdentifier { + name_token: IDENT@5..6 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@6..7 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/help.md b/crates/air_r_parser_tests/tests/corpus/expressions/help.md new file mode 100644 index 00000000..e59a717d --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/help.md @@ -0,0 +1,7 @@ +## Help operator + +```r +a ? b +a ? b <- 1 +?a +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/help.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/help.snap.md new file mode 100644 index 00000000..7fe5ea89 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/help.snap.md @@ -0,0 +1,57 @@ + + +## Help operator + +```r +a ? b +a ? b <- 1 +?a +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "a" [] [Whitespace(" ")], + }, + operator: WAT@2..4 "?" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@4..5 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@5..8 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: WAT@8..10 "?" [] [Whitespace(" ")], + right: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@10..12 "b" [] [Whitespace(" ")], + }, + operator: ASSIGN@12..15 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@15..16 "1" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: WAT@16..18 "?" [Newline("\n")] [], + argument: RIdentifier { + name_token: IDENT@18..19 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@19..20 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/namespace.md b/crates/air_r_parser_tests/tests/corpus/expressions/namespace.md new file mode 100644 index 00000000..cc9807cc --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/namespace.md @@ -0,0 +1,134 @@ +## Namespace get + +```r +foo::bar +foo::bar(1) +foo::... +foo::..1 +...::foo +..1::foo +``` + +## Namespace get internal + +```r +foo:::bar +foo:::bar(1) +foo:::... +foo:::..1 +...:::foo +..1:::foo +``` + +## Namespace with missing RHS [error] + +```r +# It's nice that `::` enforces that its RHS can only be a string or identifier, +# so this gives us a pretty clean tree even though it is invalid R code. +# r-lib/tree-sitter-r#65 +library(dplyr::) + +library() + +1 +2 +3 +``` + +## Namespace with missing RHS - 2 [error] + +```r +foo:: +``` + +```r +foo::: +``` + +## Namespace operators + +```r +a::b +a:::b + +a::`b` +a:::`b` + +a::"b" +a:::"b" + +a::b() +a:::b() + +a::b$c() +a:::b@c() + +a::... +a:::... + +...::a +...:::a + +...::... +...:::... + +a::..1 +a:::..1 + +..1::a +..1:::a + +..1::..1 +..1:::..1 +``` + +## Namespace with a literal RHS [error] + +The RHS slot is left missing rather than swallowing the invalid right-hand side. + +```r +a::1 +a::NA +a::NULL +a::TRUE +a::(b) +a::{ b } + +a:::1 +a:::NA +a:::NULL +a:::TRUE +a:::(b) +a:::{ b } +``` + +## Chained `::` [error] + +```r +a::b::c +``` + +## Chained `:::` [error] + +```r +a:::b:::c +``` + +## Call on the left of `::` [error] + +```r +a()::b +``` + +## Call on the left of `:::` [error] + +```r +a():::b +``` + +## Namespace newline is significant only outside parens + +```r +f(stats:: + median) +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/namespace.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/namespace.snap.md new file mode 100644 index 00000000..21d2ae89 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/namespace.snap.md @@ -0,0 +1,1475 @@ + + +## Namespace get + +```r +foo::bar +foo::bar(1) +foo::... +foo::..1 +...::foo +..1::foo +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + operator: COLON2@3..5 "::" [] [], + right: RIdentifier { + name_token: IDENT@5..8 "bar" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@8..12 "foo" [Newline("\n")] [], + }, + operator: COLON2@12..14 "::" [] [], + right: RIdentifier { + name_token: IDENT@14..17 "bar" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@17..18 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@18..19 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@19..20 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@20..24 "foo" [Newline("\n")] [], + }, + operator: COLON2@24..26 "::" [] [], + right: RDots { + value_token: DOTS@26..29 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@29..33 "foo" [Newline("\n")] [], + }, + operator: COLON2@33..35 "::" [] [], + right: RDotDotI { + value_token: DOTDOTI@35..38 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDots { + value_token: DOTS@38..42 "..." [Newline("\n")] [], + }, + operator: COLON2@42..44 "::" [] [], + right: RIdentifier { + name_token: IDENT@44..47 "foo" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDotDotI { + value_token: DOTDOTI@47..51 "..1" [Newline("\n")] [], + }, + operator: COLON2@51..53 "::" [] [], + right: RIdentifier { + name_token: IDENT@53..56 "foo" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@56..57 "" [Newline("\n")] [], +} +``` + +## Namespace get internal + +```r +foo:::bar +foo:::bar(1) +foo:::... +foo:::..1 +...:::foo +..1:::foo +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + operator: COLON3@3..6 ":::" [] [], + right: RIdentifier { + name_token: IDENT@6..9 "bar" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@9..13 "foo" [Newline("\n")] [], + }, + operator: COLON3@13..16 ":::" [] [], + right: RIdentifier { + name_token: IDENT@16..19 "bar" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@19..20 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@20..21 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@21..22 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@22..26 "foo" [Newline("\n")] [], + }, + operator: COLON3@26..29 ":::" [] [], + right: RDots { + value_token: DOTS@29..32 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@32..36 "foo" [Newline("\n")] [], + }, + operator: COLON3@36..39 ":::" [] [], + right: RDotDotI { + value_token: DOTDOTI@39..42 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDots { + value_token: DOTS@42..46 "..." [Newline("\n")] [], + }, + operator: COLON3@46..49 ":::" [] [], + right: RIdentifier { + name_token: IDENT@49..52 "foo" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDotDotI { + value_token: DOTDOTI@52..56 "..1" [Newline("\n")] [], + }, + operator: COLON3@56..59 ":::" [] [], + right: RIdentifier { + name_token: IDENT@59..62 "foo" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@62..63 "" [Newline("\n")] [], +} +``` + +## Namespace with missing RHS [error] + +```r +# It's nice that `::` enforces that its RHS can only be a string or identifier, +# so this gives us a pretty clean tree even though it is invalid R code. +# r-lib/tree-sitter-r#65 +library(dplyr::) + +library() + +1 +2 +3 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..185 "library" [Comments("# It's nice that `::` ..."), Newline("\n"), Comments("# so this gives us a ..."), Newline("\n"), Comments("# r-lib/tree-sitter-r#65"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@185..186 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@186..191 "dplyr" [] [], + }, + operator: COLON2@191..193 "::" [] [], + right: missing (required), + }, + }, + ], + r_paren_token: R_PAREN@193..194 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@194..203 "library" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@203..204 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@204..205 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@205..208 "1" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@208..210 "2" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@210..212 "3" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@212..213 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:4:16 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 2 │ # so this gives us a pretty clean tree even though it is invalid R code. + 3 │ # r-lib/tree-sitter-r#65 + > 4 │ library(dplyr::) + │ ^ + 5 │ + 6 │ library() + + i `::` requires a name on the right. + + 2 │ # so this gives us a pretty clean tree even though it is invalid R code. + 3 │ # r-lib/tree-sitter-r#65 + > 4 │ library(dplyr::) + │ ^^ + 5 │ + 6 │ library() +``` + +## Namespace with missing RHS - 2 [error] + +```r +foo:: +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + operator: COLON2@3..5 "::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@5..6 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × A newline is not allowed between `::` and the name that follows. + + > 1 │ foo:: + │ ^^ + 2 │ +``` + +```r +foo::: +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + operator: COLON3@3..6 ":::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@6..7 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × A newline is not allowed between `:::` and the name that follows. + + > 1 │ foo::: + │ ^^^ + 2 │ +``` + +## Namespace operators + +```r +a::b +a:::b + +a::`b` +a:::`b` + +a::"b" +a:::"b" + +a::b() +a:::b() + +a::b$c() +a:::b@c() + +a::... +a:::... + +...::a +...:::a + +...::... +...:::... + +a::..1 +a:::..1 + +..1::a +..1:::a + +..1::..1 +..1:::..1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + operator: COLON2@1..3 "::" [] [], + right: RIdentifier { + name_token: IDENT@3..4 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@4..6 "a" [Newline("\n")] [], + }, + operator: COLON3@6..9 ":::" [] [], + right: RIdentifier { + name_token: IDENT@9..10 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@10..13 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@13..15 "::" [] [], + right: RIdentifier { + name_token: IDENT@15..18 "`b`" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@18..20 "a" [Newline("\n")] [], + }, + operator: COLON3@20..23 ":::" [] [], + right: RIdentifier { + name_token: IDENT@23..26 "`b`" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@26..29 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@29..31 "::" [] [], + right: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@31..34 "\"b\"" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@34..36 "a" [Newline("\n")] [], + }, + operator: COLON3@36..39 ":::" [] [], + right: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@39..42 "\"b\"" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@42..45 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@45..47 "::" [] [], + right: RIdentifier { + name_token: IDENT@47..48 "b" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@48..49 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@49..50 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@50..52 "a" [Newline("\n")] [], + }, + operator: COLON3@52..55 ":::" [] [], + right: RIdentifier { + name_token: IDENT@55..56 "b" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@56..57 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@57..58 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RExtractExpression { + left: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@58..61 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@61..63 "::" [] [], + right: RIdentifier { + name_token: IDENT@63..64 "b" [] [], + }, + }, + operator: DOLLAR@64..65 "$" [] [], + right: RIdentifier { + name_token: IDENT@65..66 "c" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@66..67 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@67..68 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RExtractExpression { + left: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@68..70 "a" [Newline("\n")] [], + }, + operator: COLON3@70..73 ":::" [] [], + right: RIdentifier { + name_token: IDENT@73..74 "b" [] [], + }, + }, + operator: AT@74..75 "@" [] [], + right: RIdentifier { + name_token: IDENT@75..76 "c" [] [], + }, + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@76..77 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@77..78 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@78..81 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@81..83 "::" [] [], + right: RDots { + value_token: DOTS@83..86 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@86..88 "a" [Newline("\n")] [], + }, + operator: COLON3@88..91 ":::" [] [], + right: RDots { + value_token: DOTS@91..94 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDots { + value_token: DOTS@94..99 "..." [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@99..101 "::" [] [], + right: RIdentifier { + name_token: IDENT@101..102 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDots { + value_token: DOTS@102..106 "..." [Newline("\n")] [], + }, + operator: COLON3@106..109 ":::" [] [], + right: RIdentifier { + name_token: IDENT@109..110 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDots { + value_token: DOTS@110..115 "..." [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@115..117 "::" [] [], + right: RDots { + value_token: DOTS@117..120 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDots { + value_token: DOTS@120..124 "..." [Newline("\n")] [], + }, + operator: COLON3@124..127 ":::" [] [], + right: RDots { + value_token: DOTS@127..130 "..." [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@130..133 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@133..135 "::" [] [], + right: RDotDotI { + value_token: DOTDOTI@135..138 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@138..140 "a" [Newline("\n")] [], + }, + operator: COLON3@140..143 ":::" [] [], + right: RDotDotI { + value_token: DOTDOTI@143..146 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDotDotI { + value_token: DOTDOTI@146..151 "..1" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@151..153 "::" [] [], + right: RIdentifier { + name_token: IDENT@153..154 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDotDotI { + value_token: DOTDOTI@154..158 "..1" [Newline("\n")] [], + }, + operator: COLON3@158..161 ":::" [] [], + right: RIdentifier { + name_token: IDENT@161..162 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDotDotI { + value_token: DOTDOTI@162..167 "..1" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON2@167..169 "::" [] [], + right: RDotDotI { + value_token: DOTDOTI@169..172 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RDotDotI { + value_token: DOTDOTI@172..176 "..1" [Newline("\n")] [], + }, + operator: COLON3@176..179 ":::" [] [], + right: RDotDotI { + value_token: DOTDOTI@179..182 "..1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@182..183 "" [Newline("\n")] [], +} +``` + +## Namespace with a literal RHS [error] + +The RHS slot is left missing rather than swallowing the invalid right-hand side. + +```r +a::1 +a::NA +a::NULL +a::TRUE +a::(b) +a::{ b } + +a:::1 +a:::NA +a:::NULL +a:::TRUE +a:::(b) +a:::{ b } +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + operator: COLON2@1..3 "::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@3..4 "1" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@4..6 "a" [Newline("\n")] [], + }, + operator: COLON2@6..8 "::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNaExpression { + value: NA_LOGICAL_KW@8..10 "NA" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@10..12 "a" [Newline("\n")] [], + }, + operator: COLON2@12..14 "::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNullExpression { + null_token: NULL_KW@14..18 "NULL" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@18..20 "a" [Newline("\n")] [], + }, + operator: COLON2@20..22 "::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RTrueExpression { + true_token: TRUE_KW@22..26 "TRUE" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@26..28 "a" [Newline("\n")] [], + }, + operator: COLON2@28..30 "::" [] [], + right: missing (required), + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@30..31 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@31..32 "b" [] [], + }, + }, + ], + r_paren_token: R_PAREN@32..33 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@33..35 "a" [Newline("\n")] [], + }, + operator: COLON2@35..37 "::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@37..39 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@39..41 "b" [] [Whitespace(" ")], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@41..42 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@42..45 "a" [Newline("\n"), Newline("\n")] [], + }, + operator: COLON3@45..48 ":::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@48..49 "1" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@49..51 "a" [Newline("\n")] [], + }, + operator: COLON3@51..54 ":::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNaExpression { + value: NA_LOGICAL_KW@54..56 "NA" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@56..58 "a" [Newline("\n")] [], + }, + operator: COLON3@58..61 ":::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNullExpression { + null_token: NULL_KW@61..65 "NULL" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@65..67 "a" [Newline("\n")] [], + }, + operator: COLON3@67..70 ":::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RTrueExpression { + true_token: TRUE_KW@70..74 "TRUE" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@74..76 "a" [Newline("\n")] [], + }, + operator: COLON3@76..79 ":::" [] [], + right: missing (required), + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@79..80 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@80..81 "b" [] [], + }, + }, + ], + r_paren_token: R_PAREN@81..82 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@82..84 "a" [Newline("\n")] [], + }, + operator: COLON3@84..87 ":::" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@87..89 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@89..91 "b" [] [Whitespace(" ")], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@91..92 "}" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@92..93 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + > 1 │ a::1 + │ ^ + 2 │ a::NA + 3 │ a::NULL + + i `::` requires a name on the right. + + > 1 │ a::1 + │ ^^ + 2 │ a::NA + 3 │ a::NULL + +test.R:2:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 1 │ a::1 + > 2 │ a::NA + │ ^^ + 3 │ a::NULL + 4 │ a::TRUE + + i `::` requires a name on the right. + + 1 │ a::1 + > 2 │ a::NA + │ ^^ + 3 │ a::NULL + 4 │ a::TRUE + +test.R:3:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 1 │ a::1 + 2 │ a::NA + > 3 │ a::NULL + │ ^^^^ + 4 │ a::TRUE + 5 │ a::(b) + + i `::` requires a name on the right. + + 1 │ a::1 + 2 │ a::NA + > 3 │ a::NULL + │ ^^ + 4 │ a::TRUE + 5 │ a::(b) + +test.R:4:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 2 │ a::NA + 3 │ a::NULL + > 4 │ a::TRUE + │ ^^^^ + 5 │ a::(b) + 6 │ a::{ b } + + i `::` requires a name on the right. + + 2 │ a::NA + 3 │ a::NULL + > 4 │ a::TRUE + │ ^^ + 5 │ a::(b) + 6 │ a::{ b } + +test.R:5:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 3 │ a::NULL + 4 │ a::TRUE + > 5 │ a::(b) + │ ^ + 6 │ a::{ b } + 7 │ + + i `::` requires a name on the right. + + 3 │ a::NULL + 4 │ a::TRUE + > 5 │ a::(b) + │ ^^ + 6 │ a::{ b } + 7 │ + +test.R:6:4 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 4 │ a::TRUE + 5 │ a::(b) + > 6 │ a::{ b } + │ ^ + 7 │ + 8 │ a:::1 + + i `::` requires a name on the right. + + 4 │ a::TRUE + 5 │ a::(b) + > 6 │ a::{ b } + │ ^^ + 7 │ + 8 │ a:::1 + +test.R:8:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 6 │ a::{ b } + 7 │ + > 8 │ a:::1 + │ ^ + 9 │ a:::NA + 10 │ a:::NULL + + i `:::` requires a name on the right. + + 6 │ a::{ b } + 7 │ + > 8 │ a:::1 + │ ^^^ + 9 │ a:::NA + 10 │ a:::NULL + +test.R:9:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 8 │ a:::1 + > 9 │ a:::NA + │ ^^ + 10 │ a:::NULL + 11 │ a:::TRUE + + i `:::` requires a name on the right. + + 8 │ a:::1 + > 9 │ a:::NA + │ ^^^ + 10 │ a:::NULL + 11 │ a:::TRUE + +test.R:10:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 8 │ a:::1 + 9 │ a:::NA + > 10 │ a:::NULL + │ ^^^^ + 11 │ a:::TRUE + 12 │ a:::(b) + + i `:::` requires a name on the right. + + 8 │ a:::1 + 9 │ a:::NA + > 10 │ a:::NULL + │ ^^^ + 11 │ a:::TRUE + 12 │ a:::(b) + +test.R:11:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 9 │ a:::NA + 10 │ a:::NULL + > 11 │ a:::TRUE + │ ^^^^ + 12 │ a:::(b) + 13 │ a:::{ b } + + i `:::` requires a name on the right. + + 9 │ a:::NA + 10 │ a:::NULL + > 11 │ a:::TRUE + │ ^^^ + 12 │ a:::(b) + 13 │ a:::{ b } + +test.R:12:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 10 │ a:::NULL + 11 │ a:::TRUE + > 12 │ a:::(b) + │ ^ + 13 │ a:::{ b } + 14 │ + + i `:::` requires a name on the right. + + 10 │ a:::NULL + 11 │ a:::TRUE + > 12 │ a:::(b) + │ ^^^ + 13 │ a:::{ b } + 14 │ + +test.R:13:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + 11 │ a:::TRUE + 12 │ a:::(b) + > 13 │ a:::{ b } + │ ^ + 14 │ + + i `:::` requires a name on the right. + + 11 │ a:::TRUE + 12 │ a:::(b) + > 13 │ a:::{ b } + │ ^^^ + 14 │ +``` + +## Chained `::` [error] + +```r +a::b::c +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RBogusSelector { + items: [ + RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + operator: COLON2@1..3 "::" [] [], + right: RIdentifier { + name_token: IDENT@3..4 "b" [] [], + }, + }, + ], + }, + operator: COLON2@4..6 "::" [] [], + right: RIdentifier { + name_token: IDENT@6..7 "c" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + > 1 │ a::b::c + │ ^^^^ + 2 │ + + i `::` requires a name on the left. + + > 1 │ a::b::c + │ ^^ + 2 │ +``` + +## Chained `:::` [error] + +```r +a:::b:::c +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RBogusSelector { + items: [ + RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + operator: COLON3@1..4 ":::" [] [], + right: RIdentifier { + name_token: IDENT@4..5 "b" [] [], + }, + }, + ], + }, + operator: COLON3@5..8 ":::" [] [], + right: RIdentifier { + name_token: IDENT@8..9 "c" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@9..10 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + > 1 │ a:::b:::c + │ ^^^^^ + 2 │ + + i `:::` requires a name on the left. + + > 1 │ a:::b:::c + │ ^^^ + 2 │ +``` + +## Call on the left of `::` [error] + +```r +a()::b +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RBogusSelector { + items: [ + RCall { + function: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@2..3 ")" [] [], + }, + }, + ], + }, + operator: COLON2@3..5 "::" [] [], + right: RIdentifier { + name_token: IDENT@5..6 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@6..7 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + > 1 │ a()::b + │ ^^^ + 2 │ + + i `::` requires a name on the left. + + > 1 │ a()::b + │ ^^ + 2 │ +``` + +## Call on the left of `:::` [error] + +```r +a():::b +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RNamespaceExpression { + left: RBogusSelector { + items: [ + RCall { + function: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@2..3 ")" [] [], + }, + }, + ], + }, + operator: COLON3@3..6 ":::" [] [], + right: RIdentifier { + name_token: IDENT@6..7 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a name. + + > 1 │ a():::b + │ ^^^ + 2 │ + + i `:::` requires a name on the left. + + > 1 │ a():::b + │ ^^^ + 2 │ +``` + +## Namespace newline is significant only outside parens + +```r +f(stats:: + median) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..1 "f" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@2..7 "stats" [] [], + }, + operator: COLON2@7..9 "::" [] [], + right: RIdentifier { + name_token: IDENT@9..18 "median" [Newline("\n"), Whitespace(" ")] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@18..19 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@19..20 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/parenthesized.md b/crates/air_r_parser_tests/tests/corpus/expressions/parenthesized.md new file mode 100644 index 00000000..e5b7e2e2 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/parenthesized.md @@ -0,0 +1,47 @@ +## Parenthesized expression + +```r +(1) +((1)) +(1 + 1) +(fn(a, b)) +fn((a), (b)) +(function() { + body +}) +``` + +## Not a parenthesized expression 1 [error] + +```r +() +``` + +## Not a parenthesized expression 2 [error] + +```r +( + 1 + 2 +) +``` + +## Not a parenthesized expression 3 [error] + +```r +(1; 2) +``` + +## Parentheses with comments + +```r +({ + 1 + 1 +}) + +( # comment1 + # comment2 + 1 # comment3 + # comment4 +) # comment5 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/parenthesized.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/parenthesized.snap.md new file mode 100644 index 00000000..907a4a64 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/parenthesized.snap.md @@ -0,0 +1,379 @@ + + +## Parenthesized expression + +```r +(1) +((1)) +(1 + 1) +(fn(a, b)) +fn((a), (b)) +(function() { + body +}) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@0..1 "(" [] [], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..2 "1" [] [], + }, + r_paren_token: R_PAREN@2..3 ")" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@3..5 "(" [Newline("\n")] [], + body: RParenthesizedExpression { + l_paren_token: L_PAREN@5..6 "(" [] [], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@6..7 "1" [] [], + }, + r_paren_token: R_PAREN@7..8 ")" [] [], + }, + r_paren_token: R_PAREN@8..9 ")" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@9..11 "(" [Newline("\n")] [], + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@11..13 "1" [] [Whitespace(" ")], + }, + operator: PLUS@13..15 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@15..16 "1" [] [], + }, + }, + r_paren_token: R_PAREN@16..17 ")" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@17..19 "(" [Newline("\n")] [], + body: RCall { + function: RIdentifier { + name_token: IDENT@19..21 "fn" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@21..22 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@22..23 "a" [] [], + }, + }, + COMMA@23..25 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@25..26 "b" [] [], + }, + }, + ], + r_paren_token: R_PAREN@26..27 ")" [] [], + }, + }, + r_paren_token: R_PAREN@27..28 ")" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@28..31 "fn" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@31..32 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RParenthesizedExpression { + l_paren_token: L_PAREN@32..33 "(" [] [], + body: RIdentifier { + name_token: IDENT@33..34 "a" [] [], + }, + r_paren_token: R_PAREN@34..35 ")" [] [], + }, + }, + COMMA@35..37 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RParenthesizedExpression { + l_paren_token: L_PAREN@37..38 "(" [] [], + body: RIdentifier { + name_token: IDENT@38..39 "b" [] [], + }, + r_paren_token: R_PAREN@39..40 ")" [] [], + }, + }, + ], + r_paren_token: R_PAREN@40..41 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@41..43 "(" [Newline("\n")] [], + body: RFunctionDefinition { + name: FUNCTION_KW@43..51 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@51..52 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@52..54 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@54..55 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@55..62 "body" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@62..64 "}" [Newline("\n")] [], + }, + }, + r_paren_token: R_PAREN@64..65 ")" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@65..66 "" [Newline("\n")] [], +} +``` + +## Not a parenthesized expression 1 [error] + +```r +() +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@0..1 "(" [] [], + body: missing (required), + r_paren_token: R_PAREN@1..2 ")" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@2..3 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ')'. + + > 1 │ () + │ ^ + 2 │ +``` + +## Not a parenthesized expression 2 [error] + +```r +( + 1 + 2 +) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@0..1 "(" [] [], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..5 "1" [Newline("\n"), Whitespace(" ")] [], + }, + r_paren_token: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..9 "2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_PAREN@9..11 ")" [Newline("\n")] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@11..12 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:3:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `2` + + 1 │ ( + 2 │ 1 + > 3 │ 2 + │ ^ + 4 │ ) + 5 │ + + i Remove 2 + +test.R:4:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ')'. + + 2 │ 1 + 3 │ 2 + > 4 │ ) + │ ^ + 5 │ +``` + +## Not a parenthesized expression 3 [error] + +```r +(1; 2) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@0..1 "(" [] [], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..2 "1" [] [], + }, + r_paren_token: missing (required), + }, + semicolon_token: SEMICOLON@2..4 ";" [] [Whitespace(" ")], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@4..5 "2" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_PAREN@5..6 ")" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@6..7 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `;` + + > 1 │ (1; 2) + │ ^ + 2 │ + + i Remove ; + +test.R:1:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a newline or a `;` between two expressions. + + > 1 │ (1; 2) + │ ^ + 2 │ + + i The first expression ends here. + + > 1 │ (1; 2) + │ ^ + 2 │ +``` + +## Parentheses with comments + +```r +({ + 1 + 1 +}) + +( # comment1 + # comment2 + 1 # comment3 + # comment4 +) # comment5 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@0..1 "(" [] [], + body: RBracedStatements { + l_curly_token: L_CURLY@1..2 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@2..7 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: PLUS@7..9 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@9..10 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@10..12 "}" [Newline("\n")] [], + }, + r_paren_token: R_PAREN@12..13 ")" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@13..27 "(" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# comment1")], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@27..55 "1" [Newline("\n"), Whitespace(" "), Comments("# comment2"), Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")], + }, + r_paren_token: R_PAREN@55..81 ")" [Newline("\n"), Whitespace(" "), Comments("# comment4"), Newline("\n")] [Whitespace(" "), Comments("# comment5")], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@81..82 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/precedence.md b/crates/air_r_parser_tests/tests/corpus/expressions/precedence.md new file mode 100644 index 00000000..080e7cc0 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/precedence.md @@ -0,0 +1,42 @@ +## Operator precedence + +```r +2+a*2 +2+a+2 +!a + !b +a <= 2 && 2 >= d +a[1] <- foo || bar +a && b(c) && d +val <- foo %>% bar(1) %>% baz() +``` + +## Precedence tests + +```r +A$"B"^NA +a::b$c +a$b?c +``` + +## Complex expressions + +```r +repeat if (1) TRUE else repeat 42 +if (TRUE) if (FALSE) 2 else NULL +a::b$c[[d]] <- e +TRUE ~ FALSE ~ NULL ? NA ? NaN +(if (TRUE) FALSE +else NA) +a = TRUE ? FALSE +TRUE <- FALSE = NA +TRUE <- FALSE ? NA +TRUE = FALSE ? NA +TRUE ? FALSE = NA +``` + +## Subset and subset2 precedence + +```r +a[[b[1]]] +a[b[[1]]] +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/precedence.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/precedence.snap.md new file mode 100644 index 00000000..42ec3e05 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/precedence.snap.md @@ -0,0 +1,626 @@ + + +## Operator precedence + +```r +2+a*2 +2+a+2 +!a + !b +a <= 2 && 2 >= d +a[1] <- foo || bar +a && b(c) && d +val <- foo %>% bar(1) %>% baz() +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..1 "2" [] [], + }, + operator: PLUS@1..2 "+" [] [], + right: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@2..3 "a" [] [], + }, + operator: MULTIPLY@3..4 "*" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@4..5 "2" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..7 "2" [Newline("\n")] [], + }, + operator: PLUS@7..8 "+" [] [], + right: RIdentifier { + name_token: IDENT@8..9 "a" [] [], + }, + }, + operator: PLUS@9..10 "+" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@10..11 "2" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: BANG@11..13 "!" [Newline("\n")] [], + argument: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@13..15 "a" [] [Whitespace(" ")], + }, + operator: PLUS@15..17 "+" [] [Whitespace(" ")], + right: RUnaryExpression { + operator: BANG@17..18 "!" [] [], + argument: RIdentifier { + name_token: IDENT@18..19 "b" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@19..22 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: LESS_THAN_OR_EQUAL_TO@22..25 "<=" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@25..27 "2" [] [Whitespace(" ")], + }, + }, + operator: AND2@27..30 "&&" [] [Whitespace(" ")], + right: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@30..32 "2" [] [Whitespace(" ")], + }, + operator: GREATER_THAN_OR_EQUAL_TO@32..35 ">=" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@35..36 "d" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RSubset { + function: RIdentifier { + name_token: IDENT@36..38 "a" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@38..39 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@39..40 "1" [] [], + }, + }, + ], + r_brack_token: R_BRACK@40..42 "]" [] [Whitespace(" ")], + }, + }, + operator: ASSIGN@42..45 "<-" [] [Whitespace(" ")], + right: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@45..49 "foo" [] [Whitespace(" ")], + }, + operator: OR2@49..52 "||" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@52..55 "bar" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@55..58 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: AND2@58..61 "&&" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@61..62 "b" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@62..63 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@63..64 "c" [] [], + }, + }, + ], + r_paren_token: R_PAREN@64..66 ")" [] [Whitespace(" ")], + }, + }, + }, + operator: AND2@66..69 "&&" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@69..70 "d" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@70..75 "val" [Newline("\n")] [Whitespace(" ")], + }, + operator: ASSIGN@75..78 "<-" [] [Whitespace(" ")], + right: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@78..82 "foo" [] [Whitespace(" ")], + }, + operator: SPECIAL@82..86 "%>%" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@86..89 "bar" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@89..90 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@90..91 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@91..93 ")" [] [Whitespace(" ")], + }, + }, + }, + operator: SPECIAL@93..97 "%>%" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@97..100 "baz" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@100..101 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@101..102 ")" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@102..103 "" [Newline("\n")] [], +} +``` + +## Precedence tests + +```r +A$"B"^NA +a::b$c +a$b?c +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RExtractExpression { + left: RIdentifier { + name_token: IDENT@0..1 "A" [] [], + }, + operator: DOLLAR@1..2 "$" [] [], + right: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@2..5 "\"B\"" [] [], + }, + }, + operator: EXPONENTIATE@5..6 "^" [] [], + right: RNaExpression { + value: NA_LOGICAL_KW@6..8 "NA" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RExtractExpression { + left: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@8..10 "a" [Newline("\n")] [], + }, + operator: COLON2@10..12 "::" [] [], + right: RIdentifier { + name_token: IDENT@12..13 "b" [] [], + }, + }, + operator: DOLLAR@13..14 "$" [] [], + right: RIdentifier { + name_token: IDENT@14..15 "c" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RExtractExpression { + left: RIdentifier { + name_token: IDENT@15..17 "a" [Newline("\n")] [], + }, + operator: DOLLAR@17..18 "$" [] [], + right: RIdentifier { + name_token: IDENT@18..19 "b" [] [], + }, + }, + operator: WAT@19..20 "?" [] [], + right: RIdentifier { + name_token: IDENT@20..21 "c" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@21..22 "" [Newline("\n")] [], +} +``` + +## Complex expressions + +```r +repeat if (1) TRUE else repeat 42 +if (TRUE) if (FALSE) 2 else NULL +a::b$c[[d]] <- e +TRUE ~ FALSE ~ NULL ? NA ? NaN +(if (TRUE) FALSE +else NA) +a = TRUE ? FALSE +TRUE <- FALSE = NA +TRUE <- FALSE ? NA +TRUE = FALSE ? NA +TRUE ? FALSE = NA +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RRepeatStatement { + repeat_token: REPEAT_KW@0..7 "repeat" [] [Whitespace(" ")], + body: RIfStatement { + if_token: IF_KW@7..10 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@10..11 "(" [] [], + condition: RDoubleValue { + value_token: R_DOUBLE_LITERAL@11..12 "1" [] [], + }, + r_paren_token: R_PAREN@12..14 ")" [] [Whitespace(" ")], + consequence: RTrueExpression { + true_token: TRUE_KW@14..19 "TRUE" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@19..24 "else" [] [Whitespace(" ")], + alternative: RRepeatStatement { + repeat_token: REPEAT_KW@24..31 "repeat" [] [Whitespace(" ")], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@31..33 "42" [] [], + }, + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@33..37 "if" [Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@37..38 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@38..42 "TRUE" [] [], + }, + r_paren_token: R_PAREN@42..44 ")" [] [Whitespace(" ")], + consequence: RIfStatement { + if_token: IF_KW@44..47 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@47..48 "(" [] [], + condition: RFalseExpression { + false_token: FALSE_KW@48..53 "FALSE" [] [], + }, + r_paren_token: R_PAREN@53..55 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@55..57 "2" [] [Whitespace(" ")], + }, + else_clause: RElseClause { + else_token: ELSE_KW@57..62 "else" [] [Whitespace(" ")], + alternative: RNullExpression { + null_token: NULL_KW@62..66 "NULL" [] [], + }, + }, + }, + else_clause: missing (optional), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RSubset2 { + function: RExtractExpression { + left: RNamespaceExpression { + left: RIdentifier { + name_token: IDENT@66..68 "a" [Newline("\n")] [], + }, + operator: COLON2@68..70 "::" [] [], + right: RIdentifier { + name_token: IDENT@70..71 "b" [] [], + }, + }, + operator: DOLLAR@71..72 "$" [] [], + right: RIdentifier { + name_token: IDENT@72..73 "c" [] [], + }, + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@73..75 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@75..76 "d" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@76..79 "]]" [] [Whitespace(" ")], + }, + }, + operator: ASSIGN@79..82 "<-" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@82..83 "e" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RBinaryExpression { + left: RBinaryExpression { + left: RTrueExpression { + true_token: TRUE_KW@83..89 "TRUE" [Newline("\n")] [Whitespace(" ")], + }, + operator: TILDE@89..91 "~" [] [Whitespace(" ")], + right: RFalseExpression { + false_token: FALSE_KW@91..97 "FALSE" [] [Whitespace(" ")], + }, + }, + operator: TILDE@97..99 "~" [] [Whitespace(" ")], + right: RNullExpression { + null_token: NULL_KW@99..104 "NULL" [] [Whitespace(" ")], + }, + }, + operator: WAT@104..106 "?" [] [Whitespace(" ")], + right: RNaExpression { + value: NA_LOGICAL_KW@106..109 "NA" [] [Whitespace(" ")], + }, + }, + operator: WAT@109..111 "?" [] [Whitespace(" ")], + right: RNanExpression { + nan_token: NAN_KW@111..114 "NaN" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@114..116 "(" [Newline("\n")] [], + body: RIfStatement { + if_token: IF_KW@116..119 "if" [] [Whitespace(" ")], + l_paren_token: L_PAREN@119..120 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@120..124 "TRUE" [] [], + }, + r_paren_token: R_PAREN@124..126 ")" [] [Whitespace(" ")], + consequence: RFalseExpression { + false_token: FALSE_KW@126..131 "FALSE" [] [], + }, + else_clause: RElseClause { + else_token: ELSE_KW@131..137 "else" [Newline("\n")] [Whitespace(" ")], + alternative: RNaExpression { + value: NA_LOGICAL_KW@137..139 "NA" [] [], + }, + }, + }, + r_paren_token: R_PAREN@139..140 ")" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@140..143 "a" [Newline("\n")] [Whitespace(" ")], + }, + operator: EQUAL@143..145 "=" [] [Whitespace(" ")], + right: RTrueExpression { + true_token: TRUE_KW@145..150 "TRUE" [] [Whitespace(" ")], + }, + }, + operator: WAT@150..152 "?" [] [Whitespace(" ")], + right: RFalseExpression { + false_token: FALSE_KW@152..157 "FALSE" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RTrueExpression { + true_token: TRUE_KW@157..163 "TRUE" [Newline("\n")] [Whitespace(" ")], + }, + operator: ASSIGN@163..166 "<-" [] [Whitespace(" ")], + right: RBinaryExpression { + left: RFalseExpression { + false_token: FALSE_KW@166..172 "FALSE" [] [Whitespace(" ")], + }, + operator: EQUAL@172..174 "=" [] [Whitespace(" ")], + right: RNaExpression { + value: NA_LOGICAL_KW@174..176 "NA" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RTrueExpression { + true_token: TRUE_KW@176..182 "TRUE" [Newline("\n")] [Whitespace(" ")], + }, + operator: ASSIGN@182..185 "<-" [] [Whitespace(" ")], + right: RFalseExpression { + false_token: FALSE_KW@185..191 "FALSE" [] [Whitespace(" ")], + }, + }, + operator: WAT@191..193 "?" [] [Whitespace(" ")], + right: RNaExpression { + value: NA_LOGICAL_KW@193..195 "NA" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RTrueExpression { + true_token: TRUE_KW@195..201 "TRUE" [Newline("\n")] [Whitespace(" ")], + }, + operator: EQUAL@201..203 "=" [] [Whitespace(" ")], + right: RFalseExpression { + false_token: FALSE_KW@203..209 "FALSE" [] [Whitespace(" ")], + }, + }, + operator: WAT@209..211 "?" [] [Whitespace(" ")], + right: RNaExpression { + value: NA_LOGICAL_KW@211..213 "NA" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RTrueExpression { + true_token: TRUE_KW@213..219 "TRUE" [Newline("\n")] [Whitespace(" ")], + }, + operator: WAT@219..221 "?" [] [Whitespace(" ")], + right: RBinaryExpression { + left: RFalseExpression { + false_token: FALSE_KW@221..227 "FALSE" [] [Whitespace(" ")], + }, + operator: EQUAL@227..229 "=" [] [Whitespace(" ")], + right: RNaExpression { + value: NA_LOGICAL_KW@229..231 "NA" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@231..232 "" [Newline("\n")] [], +} +``` + +## Subset and subset2 precedence + +```r +a[[b[1]]] +a[b[[1]]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "a" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset { + function: RIdentifier { + name_token: IDENT@3..4 "b" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@4..5 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..6 "1" [] [], + }, + }, + ], + r_brack_token: R_BRACK@6..7 "]" [] [], + }, + }, + }, + ], + r_brack2_token: R_BRACK2@7..9 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@9..11 "a" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@11..12 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset2 { + function: RIdentifier { + name_token: IDENT@12..13 "b" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@13..15 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@15..16 "1" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@16..18 "]]" [] [], + }, + }, + }, + ], + r_brack_token: R_BRACK@18..19 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@19..20 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/specials.md b/crates/air_r_parser_tests/tests/corpus/expressions/specials.md new file mode 100644 index 00000000..87fe3735 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/specials.md @@ -0,0 +1,46 @@ +## Specials + +```r +x %% y +x %/% y +x %+% y +x %>% y +x %>% 2 %>% z +x %some text% y +x %//% y +``` + +## A backslash inside a special is ordinary text + +R's `SpecialValue` excludes only the newline, so the whole operator is one token. + +```r +x %\% y +``` + +## An unterminated special [error] + +The `%%` closes first, leaving a `%` with no partner before the newline. + +```r +x %%% y +``` + +## Pipe + +```r +x |> print() + +x |> foo() %>% bar() |> baz() + +x |> foo() |> bar() + baz() + +x |> {function(x) x}() +``` + +## Pipe placeholder + +```r +foo |> bar(x, y = _) +foo |> bar() |> baz(data = _) +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/specials.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/specials.snap.md new file mode 100644 index 00000000..1713760b --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/specials.snap.md @@ -0,0 +1,463 @@ + + +## Specials + +```r +x %% y +x %/% y +x %+% y +x %>% y +x %>% 2 %>% z +x %some text% y +x %//% y +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "x" [] [Whitespace(" ")], + }, + operator: SPECIAL@2..5 "%%" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@5..6 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@6..9 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: SPECIAL@9..13 "%/%" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@13..14 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@14..17 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: SPECIAL@17..21 "%+%" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@21..22 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@22..25 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: SPECIAL@25..29 "%>%" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@29..30 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@30..33 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: SPECIAL@33..37 "%>%" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@37..39 "2" [] [Whitespace(" ")], + }, + }, + operator: SPECIAL@39..43 "%>%" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@43..44 "z" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@44..47 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: SPECIAL@47..59 "%some text%" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@59..60 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@60..63 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: SPECIAL@63..68 "%//%" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@68..69 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@69..70 "" [Newline("\n")] [], +} +``` + +## A backslash inside a special is ordinary text + +R's `SpecialValue` excludes only the newline, so the whole operator is one token. + +```r +x %\% y +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "x" [] [Whitespace(" ")], + }, + operator: SPECIAL@2..6 "%\\%" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@6..7 "y" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +## An unterminated special [error] + +The `%%` closes first, leaving a `%` with no partner before the newline. + +```r +x %%% y +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "x" [] [Whitespace(" ")], + }, + operator: SPECIAL@2..4 "%%" [] [], + right: missing (required), + }, + operator: SPECIAL@4..7 "% y" [] [], + right: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unterminated `%` operator. + + > 1 │ x %%% y + │ ^ + 2 │ + + i A `%...%` operator must be closed by a second `%` on the same line. + +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found the end of the file. + + 1 │ x %%% y + > 2 │ + │ +``` + +## Pipe + +```r +x |> print() + +x |> foo() %>% bar() |> baz() + +x |> foo() |> bar() + baz() + +x |> {function(x) x}() +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "x" [] [Whitespace(" ")], + }, + operator: PIPE@2..5 "|>" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@5..10 "print" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@10..11 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@11..12 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@12..16 "x" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + operator: PIPE@16..19 "|>" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@19..22 "foo" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@22..23 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@23..25 ")" [] [Whitespace(" ")], + }, + }, + }, + operator: SPECIAL@25..29 "%>%" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@29..32 "bar" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@32..33 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@33..35 ")" [] [Whitespace(" ")], + }, + }, + }, + operator: PIPE@35..38 "|>" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@38..41 "baz" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@41..42 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@42..43 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@43..47 "x" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + operator: PIPE@47..50 "|>" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@50..53 "foo" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@53..54 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@54..56 ")" [] [Whitespace(" ")], + }, + }, + }, + operator: PIPE@56..59 "|>" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@59..62 "bar" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@62..63 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@63..65 ")" [] [Whitespace(" ")], + }, + }, + }, + operator: PLUS@65..67 "+" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@67..70 "baz" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@70..71 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@71..72 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@72..76 "x" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + operator: PIPE@76..79 "|>" [] [Whitespace(" ")], + right: RCall { + function: RBracedStatements { + l_curly_token: L_CURLY@79..80 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@80..88 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@88..89 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@89..90 "x" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@90..92 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@92..93 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@93..94 "}" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@94..95 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@95..96 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@96..97 "" [Newline("\n")] [], +} +``` + +## Pipe placeholder + +```r +foo |> bar(x, y = _) +foo |> bar() |> baz(data = _) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..4 "foo" [] [Whitespace(" ")], + }, + operator: PIPE@4..7 "|>" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@7..10 "bar" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@10..11 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@11..12 "x" [] [], + }, + }, + COMMA@12..14 "," [] [Whitespace(" ")], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@14..16 "y" [] [Whitespace(" ")], + }, + eq_token: EQUAL@16..18 "=" [] [Whitespace(" ")], + }, + value: RIdentifier { + name_token: IDENT@18..19 "_" [] [], + }, + }, + ], + r_paren_token: R_PAREN@19..20 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@20..25 "foo" [Newline("\n")] [Whitespace(" ")], + }, + operator: PIPE@25..28 "|>" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@28..31 "bar" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@31..32 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@32..34 ")" [] [Whitespace(" ")], + }, + }, + }, + operator: PIPE@34..37 "|>" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@37..40 "baz" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@40..41 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@41..46 "data" [] [Whitespace(" ")], + }, + eq_token: EQUAL@46..48 "=" [] [Whitespace(" ")], + }, + value: RIdentifier { + name_token: IDENT@48..49 "_" [] [], + }, + }, + ], + r_paren_token: R_PAREN@49..50 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@50..51 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser/tests/snapshots/ok/subset.R b/crates/air_r_parser_tests/tests/corpus/expressions/subset.md similarity index 58% rename from crates/air_r_parser/tests/snapshots/ok/subset.R rename to crates/air_r_parser_tests/tests/corpus/expressions/subset.md index 1ce21345..4361eb0e 100644 --- a/crates/air_r_parser/tests/snapshots/ok/subset.R +++ b/crates/air_r_parser_tests/tests/corpus/expressions/subset.md @@ -1,3 +1,34 @@ +## Subset + +```r +foo[] +foo[,] +foo[,,] +foo[x,] +foo[x,,] +foo[,x] +foo[,,x] +foo[x,,y] +foo[,,x,,y,,] + +foo[bar] +foo[1, 2] +foo[x=1,,y=3,4] +``` + +## Nested brackets + +```r +x[a[b]] +x[[a[b]]] +x[a[[b]]] +x[[a[[b]]]] +x[y[z[[a]]]] +``` + +## Subset arguments + +```r fn fn[a] fn[a, b] @@ -39,3 +70,4 @@ fn[ ] {expr}[a = 1] +``` diff --git a/crates/air_r_parser/tests/snapshots/ok/subset.R.snap b/crates/air_r_parser_tests/tests/corpus/expressions/subset.snap.md similarity index 50% rename from crates/air_r_parser/tests/snapshots/ok/subset.R.snap rename to crates/air_r_parser_tests/tests/corpus/expressions/subset.snap.md index 8829b926..a6a92214 100644 --- a/crates/air_r_parser/tests/snapshots/ok/subset.R.snap +++ b/crates/air_r_parser_tests/tests/corpus/expressions/subset.snap.md @@ -1,10 +1,592 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input + -```R +## Subset + +```r +foo[] +foo[,] +foo[,,] +foo[x,] +foo[x,,] +foo[,x] +foo[,,x] +foo[x,,y] +foo[,,x,,y,,] + +foo[bar] +foo[1, 2] +foo[x=1,,y=3,4] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@3..4 "[" [] [], + items: RArgumentList [], + r_brack_token: R_BRACK@4..5 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@5..9 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@9..10 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@10..11 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack_token: R_BRACK@11..12 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@12..16 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@16..17 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@17..18 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@18..19 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack_token: R_BRACK@19..20 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@20..24 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@24..25 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@25..26 "x" [] [], + }, + }, + COMMA@26..27 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack_token: R_BRACK@27..28 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@28..32 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@32..33 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@33..34 "x" [] [], + }, + }, + COMMA@34..35 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@35..36 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack_token: R_BRACK@36..37 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@37..41 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@41..42 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@42..43 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@43..44 "x" [] [], + }, + }, + ], + r_brack_token: R_BRACK@44..45 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@45..49 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@49..50 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@50..51 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@51..52 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@52..53 "x" [] [], + }, + }, + ], + r_brack_token: R_BRACK@53..54 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@54..58 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@58..59 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@59..60 "x" [] [], + }, + }, + COMMA@60..61 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@61..62 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@62..63 "y" [] [], + }, + }, + ], + r_brack_token: R_BRACK@63..64 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@64..68 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@68..69 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@69..70 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@70..71 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@71..72 "x" [] [], + }, + }, + COMMA@72..73 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@73..74 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@74..75 "y" [] [], + }, + }, + COMMA@75..76 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@76..77 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack_token: R_BRACK@77..78 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@78..83 "foo" [Newline("\n"), Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@83..84 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@84..87 "bar" [] [], + }, + }, + ], + r_brack_token: R_BRACK@87..88 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@88..92 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@92..93 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@93..94 "1" [] [], + }, + }, + COMMA@94..96 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@96..97 "2" [] [], + }, + }, + ], + r_brack_token: R_BRACK@97..98 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@98..102 "foo" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@102..103 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@103..104 "x" [] [], + }, + eq_token: EQUAL@104..105 "=" [] [], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@105..106 "1" [] [], + }, + }, + COMMA@106..107 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@107..108 "," [] [], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@108..109 "y" [] [], + }, + eq_token: EQUAL@109..110 "=" [] [], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@110..111 "3" [] [], + }, + }, + COMMA@111..112 "," [] [], + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@112..113 "4" [] [], + }, + }, + ], + r_brack_token: R_BRACK@113..114 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@114..115 "" [Newline("\n")] [], +} +``` + +## Nested brackets + +```r +x[a[b]] +x[[a[b]]] +x[a[[b]]] +x[[a[[b]]]] +x[y[z[[a]]]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@1..2 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset { + function: RIdentifier { + name_token: IDENT@2..3 "a" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@3..4 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@4..5 "b" [] [], + }, + }, + ], + r_brack_token: R_BRACK@5..6 "]" [] [], + }, + }, + }, + ], + r_brack_token: R_BRACK@6..7 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@7..9 "x" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@9..11 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset { + function: RIdentifier { + name_token: IDENT@11..12 "a" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@12..13 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@13..14 "b" [] [], + }, + }, + ], + r_brack_token: R_BRACK@14..15 "]" [] [], + }, + }, + }, + ], + r_brack2_token: R_BRACK2@15..17 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@17..19 "x" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@19..20 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset2 { + function: RIdentifier { + name_token: IDENT@20..21 "a" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@21..23 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@23..24 "b" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@24..26 "]]" [] [], + }, + }, + }, + ], + r_brack_token: R_BRACK@26..27 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@27..29 "x" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@29..31 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset2 { + function: RIdentifier { + name_token: IDENT@31..32 "a" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@32..34 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@34..35 "b" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@35..37 "]]" [] [], + }, + }, + }, + ], + r_brack2_token: R_BRACK2@37..39 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@39..41 "x" [Newline("\n")] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@41..42 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset { + function: RIdentifier { + name_token: IDENT@42..43 "y" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@43..44 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset2 { + function: RIdentifier { + name_token: IDENT@44..45 "z" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@45..47 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@47..48 "a" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@48..50 "]]" [] [], + }, + }, + }, + ], + r_brack_token: R_BRACK@50..51 "]" [] [], + }, + }, + }, + ], + r_brack_token: R_BRACK@51..52 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@52..53 "" [Newline("\n")] [], +} +``` + +## Subset arguments + +```r fn fn[a] fn[a, b] @@ -46,13 +628,9 @@ fn[ ] {expr}[a = 1] - ``` - -## AST - -``` +```cst RRoot { bom_token: missing (optional), statements: RStatementList [ @@ -618,387 +1196,3 @@ RRoot { eof_token: EOF@343..344 "" [Newline("\n")] [], } ``` - -## CST - -``` -0: R_ROOT@0..344 - 0: (empty) - 1: R_STATEMENT_LIST@0..343 - 0: R_EXPRESSION_STATEMENT@0..2 - 0: R_IDENTIFIER@0..2 - 0: IDENT@0..2 "fn" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@2..8 - 0: R_SUBSET@2..8 - 0: R_IDENTIFIER@2..5 - 0: IDENT@2..5 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@5..8 - 0: L_BRACK@5..6 "[" [] [] - 1: R_ARGUMENT_LIST@6..7 - 0: R_ARGUMENT@6..7 - 0: (empty) - 1: R_IDENTIFIER@6..7 - 0: IDENT@6..7 "a" [] [] - 2: R_BRACK@7..8 "]" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@8..17 - 0: R_SUBSET@8..17 - 0: R_IDENTIFIER@8..11 - 0: IDENT@8..11 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@11..17 - 0: L_BRACK@11..12 "[" [] [] - 1: R_ARGUMENT_LIST@12..16 - 0: R_ARGUMENT@12..13 - 0: (empty) - 1: R_IDENTIFIER@12..13 - 0: IDENT@12..13 "a" [] [] - 1: COMMA@13..15 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@15..16 - 0: (empty) - 1: R_IDENTIFIER@15..16 - 0: IDENT@15..16 "b" [] [] - 2: R_BRACK@16..17 "]" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@17..25 - 0: R_SUBSET@17..25 - 0: R_IDENTIFIER@17..20 - 0: IDENT@17..20 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@20..25 - 0: L_BRACK@20..21 "[" [] [] - 1: R_ARGUMENT_LIST@21..24 - 0: R_ARGUMENT@21..24 - 0: (empty) - 1: R_DOTS@21..24 - 0: DOTS@21..24 "..." [] [] - 2: R_BRACK@24..25 "]" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@25..39 - 0: R_SUBSET@25..39 - 0: R_IDENTIFIER@25..28 - 0: IDENT@25..28 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@28..39 - 0: L_BRACK@28..29 "[" [] [] - 1: R_ARGUMENT_LIST@29..38 - 0: R_ARGUMENT@29..30 - 0: (empty) - 1: R_IDENTIFIER@29..30 - 0: IDENT@29..30 "a" [] [] - 1: COMMA@30..32 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@32..35 - 0: (empty) - 1: R_DOTS@32..35 - 0: DOTS@32..35 "..." [] [] - 3: COMMA@35..37 "," [] [Whitespace(" ")] - 4: R_ARGUMENT@37..38 - 0: (empty) - 1: R_IDENTIFIER@37..38 - 0: IDENT@37..38 "b" [] [] - 2: R_BRACK@38..39 "]" [] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@39..50 - 0: R_SUBSET@39..50 - 0: R_IDENTIFIER@39..43 - 0: IDENT@39..43 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@43..50 - 0: L_BRACK@43..44 "[" [] [] - 1: R_ARGUMENT_LIST@44..49 - 0: R_ARGUMENT@44..49 - 0: R_ARGUMENT_NAME_CLAUSE@44..48 - 0: R_IDENTIFIER@44..46 - 0: IDENT@44..46 "a" [] [Whitespace(" ")] - 1: EQUAL@46..48 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@48..49 - 0: R_DOUBLE_LITERAL@48..49 "1" [] [] - 2: R_BRACK@49..50 "]" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@50..59 - 0: R_SUBSET@50..59 - 0: R_IDENTIFIER@50..53 - 0: IDENT@50..53 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@53..59 - 0: L_BRACK@53..54 "[" [] [] - 1: R_ARGUMENT_LIST@54..58 - 0: R_ARGUMENT@54..58 - 0: R_ARGUMENT_NAME_CLAUSE@54..58 - 0: R_IDENTIFIER@54..56 - 0: IDENT@54..56 "a" [] [Whitespace(" ")] - 1: EQUAL@56..58 "=" [] [Whitespace(" ")] - 1: (empty) - 2: R_BRACK@58..59 "]" [] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@59..77 - 0: R_SUBSET@59..77 - 0: R_IDENTIFIER@59..63 - 0: IDENT@59..63 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@63..77 - 0: L_BRACK@63..64 "[" [] [] - 1: R_ARGUMENT_LIST@64..76 - 0: R_ARGUMENT@64..69 - 0: R_ARGUMENT_NAME_CLAUSE@64..68 - 0: R_IDENTIFIER@64..66 - 0: IDENT@64..66 "a" [] [Whitespace(" ")] - 1: EQUAL@66..68 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@68..69 - 0: R_DOUBLE_LITERAL@68..69 "1" [] [] - 1: COMMA@69..71 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@71..76 - 0: R_ARGUMENT_NAME_CLAUSE@71..75 - 0: R_IDENTIFIER@71..73 - 0: IDENT@71..73 "b" [] [Whitespace(" ")] - 1: EQUAL@73..75 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@75..76 - 0: R_DOUBLE_LITERAL@75..76 "2" [] [] - 2: R_BRACK@76..77 "]" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@77..93 - 0: R_SUBSET@77..93 - 0: R_IDENTIFIER@77..80 - 0: IDENT@77..80 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@80..93 - 0: L_BRACK@80..81 "[" [] [] - 1: R_ARGUMENT_LIST@81..92 - 0: R_ARGUMENT@81..85 - 0: R_ARGUMENT_NAME_CLAUSE@81..85 - 0: R_IDENTIFIER@81..83 - 0: IDENT@81..83 "a" [] [Whitespace(" ")] - 1: EQUAL@83..85 "=" [] [Whitespace(" ")] - 1: (empty) - 1: COMMA@85..87 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@87..92 - 0: R_ARGUMENT_NAME_CLAUSE@87..91 - 0: R_IDENTIFIER@87..89 - 0: IDENT@87..89 "b" [] [Whitespace(" ")] - 1: EQUAL@89..91 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@91..92 - 0: R_DOUBLE_LITERAL@91..92 "2" [] [] - 2: R_BRACK@92..93 "]" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@93..106 - 0: R_SUBSET@93..106 - 0: R_IDENTIFIER@93..97 - 0: IDENT@93..97 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@97..106 - 0: L_BRACK@97..98 "[" [] [] - 1: R_ARGUMENT_LIST@98..105 - 0: R_ARGUMENT@98..105 - 0: R_ARGUMENT_NAME_CLAUSE@98..104 - 0: R_DOTS@98..102 - 0: DOTS@98..102 "..." [] [Whitespace(" ")] - 1: EQUAL@102..104 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@104..105 - 0: R_DOUBLE_LITERAL@104..105 "1" [] [] - 2: R_BRACK@105..106 "]" [] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@106..116 - 0: R_SUBSET@106..116 - 0: R_IDENTIFIER@106..109 - 0: IDENT@106..109 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@109..116 - 0: L_BRACK@109..110 "[" [] [] - 1: R_ARGUMENT_LIST@110..115 - 0: R_ARGUMENT@110..115 - 0: R_ARGUMENT_NAME_CLAUSE@110..115 - 0: R_DOTS@110..114 - 0: DOTS@110..114 "..." [] [Whitespace(" ")] - 1: EQUAL@114..115 "=" [] [] - 1: (empty) - 2: R_BRACK@115..116 "]" [] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@116..131 - 0: R_SUBSET@116..131 - 0: R_IDENTIFIER@116..120 - 0: IDENT@116..120 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@120..131 - 0: L_BRACK@120..121 "[" [] [] - 1: R_ARGUMENT_LIST@121..130 - 0: R_ARGUMENT@121..130 - 0: R_ARGUMENT_NAME_CLAUSE@121..129 - 0: R_STRING_VALUE@121..127 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@121..127 "\"arg\"" [] [Whitespace(" ")] - 1: EQUAL@127..129 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@129..130 - 0: R_DOUBLE_LITERAL@129..130 "1" [] [] - 2: R_BRACK@130..131 "]" [] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@131..143 - 0: R_SUBSET@131..143 - 0: R_IDENTIFIER@131..134 - 0: IDENT@131..134 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@134..143 - 0: L_BRACK@134..135 "[" [] [] - 1: R_ARGUMENT_LIST@135..142 - 0: R_ARGUMENT@135..142 - 0: R_ARGUMENT_NAME_CLAUSE@135..142 - 0: R_STRING_VALUE@135..141 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@135..141 "\"arg\"" [] [Whitespace(" ")] - 1: EQUAL@141..142 "=" [] [] - 1: (empty) - 2: R_BRACK@142..143 "]" [] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@143..164 - 0: R_SUBSET@143..164 - 0: R_IDENTIFIER@143..161 - 0: IDENT@143..161 "fn" [Newline("\n"), Newline("\n"), Comments("# Comma tests"), Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@161..164 - 0: L_BRACK@161..162 "[" [] [] - 1: R_ARGUMENT_LIST@162..163 - 0: R_ARGUMENT@162..162 - 0: (empty) - 1: (empty) - 1: COMMA@162..163 "," [] [] - 2: R_ARGUMENT@163..163 - 0: (empty) - 1: (empty) - 2: R_BRACK@163..164 "]" [] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@164..172 - 0: R_SUBSET@164..172 - 0: R_IDENTIFIER@164..167 - 0: IDENT@164..167 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@167..172 - 0: L_BRACK@167..168 "[" [] [] - 1: R_ARGUMENT_LIST@168..171 - 0: R_ARGUMENT@168..168 - 0: (empty) - 1: (empty) - 1: COMMA@168..169 "," [] [] - 2: R_ARGUMENT@169..169 - 0: (empty) - 1: (empty) - 3: COMMA@169..170 "," [] [] - 4: R_ARGUMENT@170..170 - 0: (empty) - 1: (empty) - 5: COMMA@170..171 "," [] [] - 6: R_ARGUMENT@171..171 - 0: (empty) - 1: (empty) - 2: R_BRACK@171..172 "]" [] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@172..185 - 0: R_SUBSET@172..185 - 0: R_IDENTIFIER@172..175 - 0: IDENT@172..175 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@175..185 - 0: L_BRACK@175..176 "[" [] [] - 1: R_ARGUMENT_LIST@176..184 - 0: R_ARGUMENT@176..176 - 0: (empty) - 1: (empty) - 1: COMMA@176..177 "," [] [] - 2: R_ARGUMENT@177..177 - 0: (empty) - 1: (empty) - 3: COMMA@177..178 "," [] [] - 4: R_ARGUMENT@178..179 - 0: (empty) - 1: R_IDENTIFIER@178..179 - 0: IDENT@178..179 "a" [] [] - 5: COMMA@179..180 "," [] [] - 6: R_ARGUMENT@180..180 - 0: (empty) - 1: (empty) - 7: COMMA@180..181 "," [] [] - 8: R_ARGUMENT@181..182 - 0: (empty) - 1: R_IDENTIFIER@181..182 - 0: IDENT@181..182 "b" [] [] - 9: COMMA@182..183 "," [] [] - 10: R_ARGUMENT@183..183 - 0: (empty) - 1: (empty) - 11: COMMA@183..184 "," [] [] - 12: R_ARGUMENT@184..184 - 0: (empty) - 1: (empty) - 2: R_BRACK@184..185 "]" [] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@185..220 - 0: R_SUBSET@185..220 - 0: R_IDENTIFIER@185..205 - 0: IDENT@185..205 "fn" [Newline("\n"), Newline("\n"), Comments("# Comment tests"), Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@205..220 - 0: L_BRACK@205..206 "[" [] [] - 1: R_ARGUMENT_LIST@206..206 - 2: R_BRACK@206..220 "]" [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@220..240 - 0: R_SUBSET@220..240 - 0: R_IDENTIFIER@220..223 - 0: IDENT@220..223 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@223..240 - 0: L_BRACK@223..224 "[" [] [] - 1: R_ARGUMENT_LIST@224..239 - 0: R_ARGUMENT@224..224 - 0: (empty) - 1: (empty) - 1: COMMA@224..225 "," [] [] - 2: R_ARGUMENT@225..225 - 0: (empty) - 1: (empty) - 3: COMMA@225..239 "," [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [] - 4: R_ARGUMENT@239..239 - 0: (empty) - 1: (empty) - 2: R_BRACK@239..240 "]" [] [] - 1: (empty) - 18: R_EXPRESSION_STATEMENT@240..277 - 0: R_SUBSET@240..277 - 0: R_IDENTIFIER@240..243 - 0: IDENT@240..243 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@243..277 - 0: L_BRACK@243..244 "[" [] [] - 1: R_ARGUMENT_LIST@244..275 - 0: R_ARGUMENT@244..248 - 0: (empty) - 1: R_IDENTIFIER@244..248 - 0: IDENT@244..248 "a" [Newline("\n"), Whitespace(" ")] [] - 1: COMMA@248..260 "," [] [Whitespace(" "), Comments("# comment1")] - 2: R_ARGUMENT@260..275 - 0: (empty) - 1: R_IDENTIFIER@260..275 - 0: IDENT@260..275 "b" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")] - 2: R_BRACK@275..277 "]" [Newline("\n")] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@277..328 - 0: R_SUBSET@277..328 - 0: R_IDENTIFIER@277..280 - 0: IDENT@277..280 "fn" [Newline("\n")] [] - 1: R_SUBSET_ARGUMENTS@280..328 - 0: L_BRACK@280..281 "[" [] [] - 1: R_ARGUMENT_LIST@281..326 - 0: R_ARGUMENT@281..326 - 0: R_ARGUMENT_NAME_CLAUSE@281..311 - 0: R_IDENTIFIER@281..296 - 0: IDENT@281..296 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment1")] - 1: EQUAL@296..311 "=" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")] - 1: R_DOUBLE_VALUE@311..326 - 0: R_DOUBLE_LITERAL@311..326 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")] - 2: R_BRACK@326..328 "]" [Newline("\n")] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@328..343 - 0: R_SUBSET@328..343 - 0: R_BRACED_STATEMENTS@328..336 - 0: L_CURLY@328..331 "{" [Newline("\n"), Newline("\n")] [] - 1: R_STATEMENT_LIST@331..335 - 0: R_EXPRESSION_STATEMENT@331..335 - 0: R_IDENTIFIER@331..335 - 0: IDENT@331..335 "expr" [] [] - 1: (empty) - 2: R_CURLY@335..336 "}" [] [] - 1: R_SUBSET_ARGUMENTS@336..343 - 0: L_BRACK@336..337 "[" [] [] - 1: R_ARGUMENT_LIST@337..342 - 0: R_ARGUMENT@337..342 - 0: R_ARGUMENT_NAME_CLAUSE@337..341 - 0: R_IDENTIFIER@337..339 - 0: IDENT@337..339 "a" [] [Whitespace(" ")] - 1: EQUAL@339..341 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@341..342 - 0: R_DOUBLE_LITERAL@341..342 "1" [] [] - 2: R_BRACK@342..343 "]" [] [] - 1: (empty) - 2: EOF@343..344 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/subset2.md b/crates/air_r_parser_tests/tests/corpus/expressions/subset2.md new file mode 100644 index 00000000..66d6a55b --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/subset2.md @@ -0,0 +1,96 @@ +## Subset2 + +```r +foo[[]] +foo[[,]] +foo[[,,]] +foo[[x,]] +foo[[x,,]] +foo[[,x]] +foo[[,,x]] +foo[[x,,y]] +foo[[,,x,,y,,]] + +foo[[x]] +foo[[x, y]] +foo[[1, 2]] +foo[[x=1,,y=3,4]] +``` + +## Unsupported subset2 with `]]` over multiple lines (r-lib/tree-sitter-r#177) [error] + +```r +x[["a"] +] +``` + +## Empty subset2 joins its `]]` + +Nothing stands between the `[[` and the `]]`, so the join has to happen on the +bump of the `[[` itself. + +```r +x[[]] +``` + +## Side by side arguments still join the `]]` [error] + +```r +x[[a b]] +``` + +## Lookahead for `=` survives the `]]` join + +Looking one token past an argument name buffers whatever follows it, which for +`x[[a]]` is the very `]` the next bump has to re-lex into `]]`. + +```r +x[[a = 1]] +x[[a = x[[b = 1]]]] +``` + +## Subset2 arguments + +```r +fn +fn[[a]] +fn[[a, b]] +fn[[...]] +fn[[a, ..., b]] + +fn[[a = 1]] +fn[[a = ]] + +fn[[a = 1, b = 2]] +fn[[a = , b = 2]] + +fn[[... = 1]] +fn[[... =]] + +fn[["arg" = 1]] +fn[["arg" =]] + +# Comma tests +fn[[,]] +fn[[,,,]] +fn[[,,a,,b,,]] + +# Comment tests +fn[[ + # comment +]] +fn[[, + # comment +,]] +fn[[ + a, # comment1 + b # comment2 +]] +fn[[ + a # comment1 + = # comment2 + 1 # comment3 +]] + +{expr}[[a = 1]] +``` diff --git a/crates/air_r_parser/tests/snapshots/ok/subset2.R.snap b/crates/air_r_parser_tests/tests/corpus/expressions/subset2.snap.md similarity index 50% rename from crates/air_r_parser/tests/snapshots/ok/subset2.R.snap rename to crates/air_r_parser_tests/tests/corpus/expressions/subset2.snap.md index e68e7980..75a4bc35 100644 --- a/crates/air_r_parser/tests/snapshots/ok/subset2.R.snap +++ b/crates/air_r_parser_tests/tests/corpus/expressions/subset2.snap.md @@ -1,10 +1,664 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input + -```R +## Subset2 + +```r +foo[[]] +foo[[,]] +foo[[,,]] +foo[[x,]] +foo[[x,,]] +foo[[,x]] +foo[[,,x]] +foo[[x,,y]] +foo[[,,x,,y,,]] + +foo[[x]] +foo[[x, y]] +foo[[1, 2]] +foo[[x=1,,y=3,4]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@3..5 "[[" [] [], + items: RArgumentList [], + r_brack2_token: R_BRACK2@5..7 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@7..11 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@11..13 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@13..14 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack2_token: R_BRACK2@14..16 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@16..20 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@20..22 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@22..23 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@23..24 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack2_token: R_BRACK2@24..26 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@26..30 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@30..32 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@32..33 "x" [] [], + }, + }, + COMMA@33..34 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack2_token: R_BRACK2@34..36 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@36..40 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@40..42 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@42..43 "x" [] [], + }, + }, + COMMA@43..44 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@44..45 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack2_token: R_BRACK2@45..47 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@47..51 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@51..53 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@53..54 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@54..55 "x" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@55..57 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@57..61 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@61..63 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@63..64 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@64..65 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@65..66 "x" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@66..68 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@68..72 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@72..74 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@74..75 "x" [] [], + }, + }, + COMMA@75..76 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@76..77 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@77..78 "y" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@78..80 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@80..84 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@84..86 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@86..87 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@87..88 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@88..89 "x" [] [], + }, + }, + COMMA@89..90 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@90..91 "," [] [], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@91..92 "y" [] [], + }, + }, + COMMA@92..93 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@93..94 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + ], + r_brack2_token: R_BRACK2@94..96 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@96..101 "foo" [Newline("\n"), Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@101..103 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@103..104 "x" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@104..106 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@106..110 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@110..112 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@112..113 "x" [] [], + }, + }, + COMMA@113..115 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@115..116 "y" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@116..118 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@118..122 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@122..124 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@124..125 "1" [] [], + }, + }, + COMMA@125..127 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@127..128 "2" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@128..130 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@130..134 "foo" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@134..136 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@136..137 "x" [] [], + }, + eq_token: EQUAL@137..138 "=" [] [], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@138..139 "1" [] [], + }, + }, + COMMA@139..140 "," [] [], + RArgument { + name_clause: missing (optional), + value: missing (optional), + }, + COMMA@140..141 "," [] [], + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@141..142 "y" [] [], + }, + eq_token: EQUAL@142..143 "=" [] [], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@143..144 "3" [] [], + }, + }, + COMMA@144..145 "," [] [], + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@145..146 "4" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@146..148 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@148..149 "" [Newline("\n")] [], +} +``` + +## Unsupported subset2 with `]]` over multiple lines (r-lib/tree-sitter-r#177) [error] + +```r +x[["a"] +] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@3..6 "\"a\"" [] [], + }, + }, + ], + r_brack2_token: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_BRACK@6..7 "]" [] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_BRACK@7..9 "]" [Newline("\n")] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@9..10 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `]]` but instead found `]` + + > 1 │ x[["a"] + │ ^ + 2 │ ] + 3 │ + + i Remove ] + +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ']'. + + 1 │ x[["a"] + > 2 │ ] + │ ^ + 3 │ +``` + +## Empty subset2 joins its `]]` + +Nothing stands between the `[[` and the `]]`, so the join has to happen on the +bump of the `[[` itself. + +```r +x[[]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [], + r_brack2_token: R_BRACK2@3..5 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@5..6 "" [Newline("\n")] [], +} +``` + +## Side by side arguments still join the `]]` [error] + +```r +x[[a b]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@3..5 "a" [] [Whitespace(" ")], + }, + }, + missing separator, + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@5..6 "b" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@6..8 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@8..9 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:6 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `b` + + > 1 │ x[[a b]] + │ ^ + 2 │ + + i Remove b +``` + +## Lookahead for `=` survives the `]]` join + +Looking one token past an argument name buffers whatever follows it, which for +`x[[a]]` is the very `]` the next bump has to re-lex into `]]`. + +```r +x[[a = 1]] +x[[a = x[[b = 1]]]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@3..5 "a" [] [Whitespace(" ")], + }, + eq_token: EQUAL@5..7 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@7..8 "1" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@8..10 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@10..12 "x" [Newline("\n")] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@12..14 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@14..16 "a" [] [Whitespace(" ")], + }, + eq_token: EQUAL@16..18 "=" [] [Whitespace(" ")], + }, + value: RSubset2 { + function: RIdentifier { + name_token: IDENT@18..19 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@19..21 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@21..23 "b" [] [Whitespace(" ")], + }, + eq_token: EQUAL@23..25 "=" [] [Whitespace(" ")], + }, + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@25..26 "1" [] [], + }, + }, + ], + r_brack2_token: R_BRACK2@26..28 "]]" [] [], + }, + }, + }, + ], + r_brack2_token: R_BRACK2@28..30 "]]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@30..31 "" [Newline("\n")] [], +} +``` + +## Subset2 arguments + +```r fn fn[[a]] fn[[a, b]] @@ -46,13 +700,9 @@ fn[[ ]] {expr}[[a = 1]] - ``` - -## AST - -``` +```cst RRoot { bom_token: missing (optional), statements: RStatementList [ @@ -618,387 +1268,3 @@ RRoot { eof_token: EOF@383..384 "" [Newline("\n")] [], } ``` - -## CST - -``` -0: R_ROOT@0..384 - 0: (empty) - 1: R_STATEMENT_LIST@0..383 - 0: R_EXPRESSION_STATEMENT@0..2 - 0: R_IDENTIFIER@0..2 - 0: IDENT@0..2 "fn" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@2..10 - 0: R_SUBSET2@2..10 - 0: R_IDENTIFIER@2..5 - 0: IDENT@2..5 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@5..10 - 0: L_BRACK2@5..7 "[[" [] [] - 1: R_ARGUMENT_LIST@7..8 - 0: R_ARGUMENT@7..8 - 0: (empty) - 1: R_IDENTIFIER@7..8 - 0: IDENT@7..8 "a" [] [] - 2: R_BRACK2@8..10 "]]" [] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@10..21 - 0: R_SUBSET2@10..21 - 0: R_IDENTIFIER@10..13 - 0: IDENT@10..13 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@13..21 - 0: L_BRACK2@13..15 "[[" [] [] - 1: R_ARGUMENT_LIST@15..19 - 0: R_ARGUMENT@15..16 - 0: (empty) - 1: R_IDENTIFIER@15..16 - 0: IDENT@15..16 "a" [] [] - 1: COMMA@16..18 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@18..19 - 0: (empty) - 1: R_IDENTIFIER@18..19 - 0: IDENT@18..19 "b" [] [] - 2: R_BRACK2@19..21 "]]" [] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@21..31 - 0: R_SUBSET2@21..31 - 0: R_IDENTIFIER@21..24 - 0: IDENT@21..24 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@24..31 - 0: L_BRACK2@24..26 "[[" [] [] - 1: R_ARGUMENT_LIST@26..29 - 0: R_ARGUMENT@26..29 - 0: (empty) - 1: R_DOTS@26..29 - 0: DOTS@26..29 "..." [] [] - 2: R_BRACK2@29..31 "]]" [] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@31..47 - 0: R_SUBSET2@31..47 - 0: R_IDENTIFIER@31..34 - 0: IDENT@31..34 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@34..47 - 0: L_BRACK2@34..36 "[[" [] [] - 1: R_ARGUMENT_LIST@36..45 - 0: R_ARGUMENT@36..37 - 0: (empty) - 1: R_IDENTIFIER@36..37 - 0: IDENT@36..37 "a" [] [] - 1: COMMA@37..39 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@39..42 - 0: (empty) - 1: R_DOTS@39..42 - 0: DOTS@39..42 "..." [] [] - 3: COMMA@42..44 "," [] [Whitespace(" ")] - 4: R_ARGUMENT@44..45 - 0: (empty) - 1: R_IDENTIFIER@44..45 - 0: IDENT@44..45 "b" [] [] - 2: R_BRACK2@45..47 "]]" [] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@47..60 - 0: R_SUBSET2@47..60 - 0: R_IDENTIFIER@47..51 - 0: IDENT@47..51 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@51..60 - 0: L_BRACK2@51..53 "[[" [] [] - 1: R_ARGUMENT_LIST@53..58 - 0: R_ARGUMENT@53..58 - 0: R_ARGUMENT_NAME_CLAUSE@53..57 - 0: R_IDENTIFIER@53..55 - 0: IDENT@53..55 "a" [] [Whitespace(" ")] - 1: EQUAL@55..57 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@57..58 - 0: R_DOUBLE_LITERAL@57..58 "1" [] [] - 2: R_BRACK2@58..60 "]]" [] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@60..71 - 0: R_SUBSET2@60..71 - 0: R_IDENTIFIER@60..63 - 0: IDENT@60..63 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@63..71 - 0: L_BRACK2@63..65 "[[" [] [] - 1: R_ARGUMENT_LIST@65..69 - 0: R_ARGUMENT@65..69 - 0: R_ARGUMENT_NAME_CLAUSE@65..69 - 0: R_IDENTIFIER@65..67 - 0: IDENT@65..67 "a" [] [Whitespace(" ")] - 1: EQUAL@67..69 "=" [] [Whitespace(" ")] - 1: (empty) - 2: R_BRACK2@69..71 "]]" [] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@71..91 - 0: R_SUBSET2@71..91 - 0: R_IDENTIFIER@71..75 - 0: IDENT@71..75 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@75..91 - 0: L_BRACK2@75..77 "[[" [] [] - 1: R_ARGUMENT_LIST@77..89 - 0: R_ARGUMENT@77..82 - 0: R_ARGUMENT_NAME_CLAUSE@77..81 - 0: R_IDENTIFIER@77..79 - 0: IDENT@77..79 "a" [] [Whitespace(" ")] - 1: EQUAL@79..81 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@81..82 - 0: R_DOUBLE_LITERAL@81..82 "1" [] [] - 1: COMMA@82..84 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@84..89 - 0: R_ARGUMENT_NAME_CLAUSE@84..88 - 0: R_IDENTIFIER@84..86 - 0: IDENT@84..86 "b" [] [Whitespace(" ")] - 1: EQUAL@86..88 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@88..89 - 0: R_DOUBLE_LITERAL@88..89 "2" [] [] - 2: R_BRACK2@89..91 "]]" [] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@91..109 - 0: R_SUBSET2@91..109 - 0: R_IDENTIFIER@91..94 - 0: IDENT@91..94 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@94..109 - 0: L_BRACK2@94..96 "[[" [] [] - 1: R_ARGUMENT_LIST@96..107 - 0: R_ARGUMENT@96..100 - 0: R_ARGUMENT_NAME_CLAUSE@96..100 - 0: R_IDENTIFIER@96..98 - 0: IDENT@96..98 "a" [] [Whitespace(" ")] - 1: EQUAL@98..100 "=" [] [Whitespace(" ")] - 1: (empty) - 1: COMMA@100..102 "," [] [Whitespace(" ")] - 2: R_ARGUMENT@102..107 - 0: R_ARGUMENT_NAME_CLAUSE@102..106 - 0: R_IDENTIFIER@102..104 - 0: IDENT@102..104 "b" [] [Whitespace(" ")] - 1: EQUAL@104..106 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@106..107 - 0: R_DOUBLE_LITERAL@106..107 "2" [] [] - 2: R_BRACK2@107..109 "]]" [] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@109..124 - 0: R_SUBSET2@109..124 - 0: R_IDENTIFIER@109..113 - 0: IDENT@109..113 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@113..124 - 0: L_BRACK2@113..115 "[[" [] [] - 1: R_ARGUMENT_LIST@115..122 - 0: R_ARGUMENT@115..122 - 0: R_ARGUMENT_NAME_CLAUSE@115..121 - 0: R_DOTS@115..119 - 0: DOTS@115..119 "..." [] [Whitespace(" ")] - 1: EQUAL@119..121 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@121..122 - 0: R_DOUBLE_LITERAL@121..122 "1" [] [] - 2: R_BRACK2@122..124 "]]" [] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@124..136 - 0: R_SUBSET2@124..136 - 0: R_IDENTIFIER@124..127 - 0: IDENT@124..127 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@127..136 - 0: L_BRACK2@127..129 "[[" [] [] - 1: R_ARGUMENT_LIST@129..134 - 0: R_ARGUMENT@129..134 - 0: R_ARGUMENT_NAME_CLAUSE@129..134 - 0: R_DOTS@129..133 - 0: DOTS@129..133 "..." [] [Whitespace(" ")] - 1: EQUAL@133..134 "=" [] [] - 1: (empty) - 2: R_BRACK2@134..136 "]]" [] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@136..153 - 0: R_SUBSET2@136..153 - 0: R_IDENTIFIER@136..140 - 0: IDENT@136..140 "fn" [Newline("\n"), Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@140..153 - 0: L_BRACK2@140..142 "[[" [] [] - 1: R_ARGUMENT_LIST@142..151 - 0: R_ARGUMENT@142..151 - 0: R_ARGUMENT_NAME_CLAUSE@142..150 - 0: R_STRING_VALUE@142..148 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@142..148 "\"arg\"" [] [Whitespace(" ")] - 1: EQUAL@148..150 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@150..151 - 0: R_DOUBLE_LITERAL@150..151 "1" [] [] - 2: R_BRACK2@151..153 "]]" [] [] - 1: (empty) - 12: R_EXPRESSION_STATEMENT@153..167 - 0: R_SUBSET2@153..167 - 0: R_IDENTIFIER@153..156 - 0: IDENT@153..156 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@156..167 - 0: L_BRACK2@156..158 "[[" [] [] - 1: R_ARGUMENT_LIST@158..165 - 0: R_ARGUMENT@158..165 - 0: R_ARGUMENT_NAME_CLAUSE@158..165 - 0: R_STRING_VALUE@158..164 - 0: R_DOUBLE_QUOTED_STRING_LITERAL@158..164 "\"arg\"" [] [Whitespace(" ")] - 1: EQUAL@164..165 "=" [] [] - 1: (empty) - 2: R_BRACK2@165..167 "]]" [] [] - 1: (empty) - 13: R_EXPRESSION_STATEMENT@167..190 - 0: R_SUBSET2@167..190 - 0: R_IDENTIFIER@167..185 - 0: IDENT@167..185 "fn" [Newline("\n"), Newline("\n"), Comments("# Comma tests"), Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@185..190 - 0: L_BRACK2@185..187 "[[" [] [] - 1: R_ARGUMENT_LIST@187..188 - 0: R_ARGUMENT@187..187 - 0: (empty) - 1: (empty) - 1: COMMA@187..188 "," [] [] - 2: R_ARGUMENT@188..188 - 0: (empty) - 1: (empty) - 2: R_BRACK2@188..190 "]]" [] [] - 1: (empty) - 14: R_EXPRESSION_STATEMENT@190..200 - 0: R_SUBSET2@190..200 - 0: R_IDENTIFIER@190..193 - 0: IDENT@190..193 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@193..200 - 0: L_BRACK2@193..195 "[[" [] [] - 1: R_ARGUMENT_LIST@195..198 - 0: R_ARGUMENT@195..195 - 0: (empty) - 1: (empty) - 1: COMMA@195..196 "," [] [] - 2: R_ARGUMENT@196..196 - 0: (empty) - 1: (empty) - 3: COMMA@196..197 "," [] [] - 4: R_ARGUMENT@197..197 - 0: (empty) - 1: (empty) - 5: COMMA@197..198 "," [] [] - 6: R_ARGUMENT@198..198 - 0: (empty) - 1: (empty) - 2: R_BRACK2@198..200 "]]" [] [] - 1: (empty) - 15: R_EXPRESSION_STATEMENT@200..215 - 0: R_SUBSET2@200..215 - 0: R_IDENTIFIER@200..203 - 0: IDENT@200..203 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@203..215 - 0: L_BRACK2@203..205 "[[" [] [] - 1: R_ARGUMENT_LIST@205..213 - 0: R_ARGUMENT@205..205 - 0: (empty) - 1: (empty) - 1: COMMA@205..206 "," [] [] - 2: R_ARGUMENT@206..206 - 0: (empty) - 1: (empty) - 3: COMMA@206..207 "," [] [] - 4: R_ARGUMENT@207..208 - 0: (empty) - 1: R_IDENTIFIER@207..208 - 0: IDENT@207..208 "a" [] [] - 5: COMMA@208..209 "," [] [] - 6: R_ARGUMENT@209..209 - 0: (empty) - 1: (empty) - 7: COMMA@209..210 "," [] [] - 8: R_ARGUMENT@210..211 - 0: (empty) - 1: R_IDENTIFIER@210..211 - 0: IDENT@210..211 "b" [] [] - 9: COMMA@211..212 "," [] [] - 10: R_ARGUMENT@212..212 - 0: (empty) - 1: (empty) - 11: COMMA@212..213 "," [] [] - 12: R_ARGUMENT@213..213 - 0: (empty) - 1: (empty) - 2: R_BRACK2@213..215 "]]" [] [] - 1: (empty) - 16: R_EXPRESSION_STATEMENT@215..252 - 0: R_SUBSET2@215..252 - 0: R_IDENTIFIER@215..235 - 0: IDENT@215..235 "fn" [Newline("\n"), Newline("\n"), Comments("# Comment tests"), Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@235..252 - 0: L_BRACK2@235..237 "[[" [] [] - 1: R_ARGUMENT_LIST@237..237 - 2: R_BRACK2@237..252 "]]" [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [] - 1: (empty) - 17: R_EXPRESSION_STATEMENT@252..274 - 0: R_SUBSET2@252..274 - 0: R_IDENTIFIER@252..255 - 0: IDENT@252..255 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@255..274 - 0: L_BRACK2@255..257 "[[" [] [] - 1: R_ARGUMENT_LIST@257..272 - 0: R_ARGUMENT@257..257 - 0: (empty) - 1: (empty) - 1: COMMA@257..258 "," [] [] - 2: R_ARGUMENT@258..258 - 0: (empty) - 1: (empty) - 3: COMMA@258..272 "," [Newline("\n"), Whitespace(" "), Comments("# comment"), Newline("\n")] [] - 4: R_ARGUMENT@272..272 - 0: (empty) - 1: (empty) - 2: R_BRACK2@272..274 "]]" [] [] - 1: (empty) - 18: R_EXPRESSION_STATEMENT@274..313 - 0: R_SUBSET2@274..313 - 0: R_IDENTIFIER@274..277 - 0: IDENT@274..277 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@277..313 - 0: L_BRACK2@277..279 "[[" [] [] - 1: R_ARGUMENT_LIST@279..310 - 0: R_ARGUMENT@279..283 - 0: (empty) - 1: R_IDENTIFIER@279..283 - 0: IDENT@279..283 "a" [Newline("\n"), Whitespace(" ")] [] - 1: COMMA@283..295 "," [] [Whitespace(" "), Comments("# comment1")] - 2: R_ARGUMENT@295..310 - 0: (empty) - 1: R_IDENTIFIER@295..310 - 0: IDENT@295..310 "b" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")] - 2: R_BRACK2@310..313 "]]" [Newline("\n")] [] - 1: (empty) - 19: R_EXPRESSION_STATEMENT@313..366 - 0: R_SUBSET2@313..366 - 0: R_IDENTIFIER@313..316 - 0: IDENT@313..316 "fn" [Newline("\n")] [] - 1: R_SUBSET2_ARGUMENTS@316..366 - 0: L_BRACK2@316..318 "[[" [] [] - 1: R_ARGUMENT_LIST@318..363 - 0: R_ARGUMENT@318..363 - 0: R_ARGUMENT_NAME_CLAUSE@318..348 - 0: R_IDENTIFIER@318..333 - 0: IDENT@318..333 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment1")] - 1: EQUAL@333..348 "=" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment2")] - 1: R_DOUBLE_VALUE@348..363 - 0: R_DOUBLE_LITERAL@348..363 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")] - 2: R_BRACK2@363..366 "]]" [Newline("\n")] [] - 1: (empty) - 20: R_EXPRESSION_STATEMENT@366..383 - 0: R_SUBSET2@366..383 - 0: R_BRACED_STATEMENTS@366..374 - 0: L_CURLY@366..369 "{" [Newline("\n"), Newline("\n")] [] - 1: R_STATEMENT_LIST@369..373 - 0: R_EXPRESSION_STATEMENT@369..373 - 0: R_IDENTIFIER@369..373 - 0: IDENT@369..373 "expr" [] [] - 1: (empty) - 2: R_CURLY@373..374 "}" [] [] - 1: R_SUBSET2_ARGUMENTS@374..383 - 0: L_BRACK2@374..376 "[[" [] [] - 1: R_ARGUMENT_LIST@376..381 - 0: R_ARGUMENT@376..381 - 0: R_ARGUMENT_NAME_CLAUSE@376..380 - 0: R_IDENTIFIER@376..378 - 0: IDENT@376..378 "a" [] [Whitespace(" ")] - 1: EQUAL@378..380 "=" [] [Whitespace(" ")] - 1: R_DOUBLE_VALUE@380..381 - 0: R_DOUBLE_LITERAL@380..381 "1" [] [] - 2: R_BRACK2@381..383 "]]" [] [] - 1: (empty) - 2: EOF@383..384 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/unary.md b/crates/air_r_parser_tests/tests/corpus/expressions/unary.md new file mode 100644 index 00000000..99381a50 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/unary.md @@ -0,0 +1,36 @@ +## Unary operators + +```r +!a ++a +-a +foo(!a, +b) +foo(-a, bar) + +! + a +- b +``` + +## Unary operators across lines + +```r ++1 +-1 +~1 +?1 +!1 + ++ +1 + ++ # Comment +1 + ++ +# Comment ++ # Comment +1 + +1 + ++1 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/expressions/unary.snap.md b/crates/air_r_parser_tests/tests/corpus/expressions/unary.snap.md new file mode 100644 index 00000000..421d5c63 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/expressions/unary.snap.md @@ -0,0 +1,257 @@ + + +## Unary operators + +```r +!a ++a +-a +foo(!a, +b) +foo(-a, bar) + +! + a +- b +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RUnaryExpression { + operator: BANG@0..1 "!" [] [], + argument: RIdentifier { + name_token: IDENT@1..2 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: PLUS@2..4 "+" [Newline("\n")] [], + argument: RIdentifier { + name_token: IDENT@4..5 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: MINUS@5..7 "-" [Newline("\n")] [], + argument: RIdentifier { + name_token: IDENT@7..8 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@8..12 "foo" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@12..13 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RUnaryExpression { + operator: BANG@13..14 "!" [] [], + argument: RIdentifier { + name_token: IDENT@14..15 "a" [] [], + }, + }, + }, + COMMA@15..17 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RUnaryExpression { + operator: PLUS@17..18 "+" [] [], + argument: RIdentifier { + name_token: IDENT@18..19 "b" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@19..20 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@20..24 "foo" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@24..25 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RUnaryExpression { + operator: MINUS@25..26 "-" [] [], + argument: RIdentifier { + name_token: IDENT@26..27 "a" [] [], + }, + }, + }, + COMMA@27..29 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@29..32 "bar" [] [], + }, + }, + ], + r_paren_token: R_PAREN@32..33 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: BANG@33..36 "!" [Newline("\n"), Newline("\n")] [], + argument: RIdentifier { + name_token: IDENT@36..40 "a" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: MINUS@40..44 "-" [Newline("\n")] [Whitespace(" ")], + argument: RIdentifier { + name_token: IDENT@44..45 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@45..46 "" [Newline("\n")] [], +} +``` + +## Unary operators across lines + +```r ++1 +-1 +~1 +?1 +!1 + ++ +1 + ++ # Comment +1 + ++ +# Comment ++ # Comment +1 + +1 + ++1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RUnaryExpression { + operator: PLUS@0..1 "+" [] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..2 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: MINUS@2..4 "-" [Newline("\n")] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@4..5 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: TILDE@5..7 "~" [Newline("\n")] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@7..8 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: WAT@8..10 "?" [Newline("\n")] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@10..11 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: BANG@11..13 "!" [Newline("\n")] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@13..14 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: PLUS@14..17 "+" [Newline("\n"), Newline("\n")] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@17..19 "1" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: PLUS@19..32 "+" [Newline("\n"), Newline("\n")] [Whitespace(" "), Comments("# Comment")], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@32..34 "1" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: PLUS@34..37 "+" [Newline("\n"), Newline("\n")] [], + argument: RUnaryExpression { + operator: PLUS@37..59 "+" [Newline("\n"), Comments("# Comment"), Newline("\n")] [Whitespace(" "), Comments("# Comment")], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@59..61 "1" [Newline("\n")] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@61..65 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + operator: PLUS@65..67 "+" [] [Whitespace(" ")], + right: RUnaryExpression { + operator: PLUS@67..68 "+" [] [], + argument: RUnaryExpression { + operator: PLUS@68..69 "+" [] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@69..70 "1" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@70..71 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/functions/definition.md b/crates/air_r_parser_tests/tests/corpus/functions/definition.md new file mode 100644 index 00000000..c93207df --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/functions/definition.md @@ -0,0 +1,106 @@ +## Functions + +```r +function() 1 +function() {} +function(arg1, arg2) { + arg2 +} + +function(arg1, arg2 = 2) {} + +function(x, + y, + z = 3) { + +} + +function() + + + 1 + +function +() 1 + 1 + +function + +() 1 + 1 + +function + +# this important comment + +() 1 + 1 + +function() function() {} + +function(x = function() {}) {} + +# With no intermediate `{` scope +function() for(i in 1:5) i +``` + +## Function no body [error] + +```r +function(x, y) +``` + +## Function no body with assignment [error] + +```r +x <- function(x, y) +``` + +## Function no body inside another function [error] + +```r +function(x = function()) {} +``` + +## Function definitions + +```r +function(a) a +function(a, b) a + b +function( + a # important! +) a +function(...) 1 +function(a, ..., b) 1 +function(a = 1, ..., b = 2) a +function(..1, ..2) get("..1") + get("..2") +function(..1 = 1, ..2 = 2) get("..1") + get("..2") +function( + x = # important! + 4 +) 1 + +\(x, y) 1 +``` + +## A body stops at the binary `?` + +Weirdly in R, help is precedence 1 and function and loop bodies are precedence 2. +So `function() x ? y` must parse as `(function () x) ? y`. + +```r +function() a ? b +function() a ? b ? c +?function() a +``` + +```r +function() a <- b +function() a = b +1 + function(x) x + 2 +``` + +## A definition with no parameter list [error] + +```r +function 1 +\ 1 +function +``` diff --git a/crates/air_r_parser_tests/tests/corpus/functions/definition.snap.md b/crates/air_r_parser_tests/tests/corpus/functions/definition.snap.md new file mode 100644 index 00000000..38f2a637 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/functions/definition.snap.md @@ -0,0 +1,1173 @@ + + +## Functions + +```r +function() 1 +function() {} +function(arg1, arg2) { + arg2 +} + +function(arg1, arg2 = 2) {} + +function(x, + y, + z = 3) { + +} + +function() + + + 1 + +function +() 1 + 1 + +function + +() 1 + 1 + +function + +# this important comment + +() 1 + 1 + +function() function() {} + +function(x = function() {}) {} + +# With no intermediate `{` scope +function() for(i in 1:5) i +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@9..11 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@11..12 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@12..21 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@21..22 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@22..24 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@24..25 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@25..26 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@26..35 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@35..36 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@36..40 "arg1" [] [], + }, + default: missing (optional), + }, + COMMA@40..42 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@42..46 "arg2" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@46..48 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@48..49 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@49..56 "arg2" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@56..58 "}" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@58..68 "function" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@68..69 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@69..73 "arg1" [] [], + }, + default: missing (optional), + }, + COMMA@73..75 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@75..80 "arg2" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@80..82 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@82..83 "2" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@83..85 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@85..86 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@86..87 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@87..97 "function" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@97..98 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@98..99 "x" [] [], + }, + default: missing (optional), + }, + COMMA@99..100 "," [] [], + RParameter { + name: RIdentifier { + name_token: IDENT@100..111 "y" [Newline("\n"), Whitespace(" ")] [], + }, + default: missing (optional), + }, + COMMA@111..112 "," [] [], + RParameter { + name: RIdentifier { + name_token: IDENT@112..124 "z" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@124..126 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@126..127 "3" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@127..129 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@129..130 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@130..133 "}" [Newline("\n"), Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@133..143 "function" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@143..144 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@144..145 ")" [] [], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@145..151 "1" [Newline("\n"), Newline("\n"), Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@151..161 "function" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@161..163 "(" [Newline("\n")] [], + items: RParameterList [], + r_paren_token: R_PAREN@163..165 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@165..167 "1" [] [Whitespace(" ")], + }, + operator: PLUS@167..169 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@169..170 "1" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@170..180 "function" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@180..183 "(" [Newline("\n"), Newline("\n")] [], + items: RParameterList [], + r_paren_token: R_PAREN@183..185 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@185..187 "1" [] [Whitespace(" ")], + }, + operator: PLUS@187..189 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@189..190 "1" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@190..200 "function" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@200..229 "(" [Newline("\n"), Newline("\n"), Comments("# this important comment"), Newline("\n"), Newline("\n")] [], + items: RParameterList [], + r_paren_token: R_PAREN@229..231 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@231..233 "1" [] [Whitespace(" ")], + }, + operator: PLUS@233..235 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@235..236 "1" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@236..246 "function" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@246..247 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@247..249 ")" [] [Whitespace(" ")], + }, + body: RFunctionDefinition { + name: FUNCTION_KW@249..257 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@257..258 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@258..260 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@260..261 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@261..262 "}" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@262..272 "function" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@272..273 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@273..275 "x" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@275..277 "=" [] [Whitespace(" ")], + value: RFunctionDefinition { + name: FUNCTION_KW@277..285 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@285..286 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@286..288 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@288..289 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@289..290 "}" [] [], + }, + }, + }, + }, + ], + r_paren_token: R_PAREN@290..292 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@292..293 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@293..294 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@294..337 "function" [Newline("\n"), Newline("\n"), Comments("# With no intermediat ..."), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@337..338 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@338..340 ")" [] [Whitespace(" ")], + }, + body: RForStatement { + for_token: FOR_KW@340..343 "for" [] [], + l_paren_token: L_PAREN@343..344 "(" [] [], + variable: RIdentifier { + name_token: IDENT@344..346 "i" [] [Whitespace(" ")], + }, + in_token: IN_KW@346..349 "in" [] [Whitespace(" ")], + sequence: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@349..350 "1" [] [], + }, + operator: COLON@350..351 ":" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@351..352 "5" [] [], + }, + }, + r_paren_token: R_PAREN@352..354 ")" [] [Whitespace(" ")], + body: RIdentifier { + name_token: IDENT@354..355 "i" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@355..356 "" [Newline("\n")] [], +} +``` + +## Function no body [error] + +```r +function(x, y) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@9..10 "x" [] [], + }, + default: missing (optional), + }, + COMMA@10..12 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@12..13 "y" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@13..14 ")" [] [], + }, + body: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@14..15 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a function body but instead found the end of the file. + + 1 │ function(x, y) + > 2 │ + │ +``` + +## Function no body with assignment [error] + +```r +x <- function(x, y) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "x" [] [Whitespace(" ")], + }, + operator: ASSIGN@2..5 "<-" [] [Whitespace(" ")], + right: RFunctionDefinition { + name: FUNCTION_KW@5..13 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@13..14 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@14..15 "x" [] [], + }, + default: missing (optional), + }, + COMMA@15..17 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@17..18 "y" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@18..19 ")" [] [], + }, + body: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@19..20 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a function body but instead found the end of the file. + + 1 │ x <- function(x, y) + > 2 │ + │ +``` + +## Function no body inside another function [error] + +```r +function(x = function()) {} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@9..11 "x" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@11..13 "=" [] [Whitespace(" ")], + value: RFunctionDefinition { + name: FUNCTION_KW@13..21 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@21..22 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@22..23 ")" [] [], + }, + body: missing (required), + }, + }, + }, + ], + r_paren_token: R_PAREN@23..25 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@25..26 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@26..27 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@27..28 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:24 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a function body but instead found ')'. + + > 1 │ function(x = function()) {} + │ ^ + 2 │ +``` + +## Function definitions + +```r +function(a) a +function(a, b) a + b +function( + a # important! +) a +function(...) 1 +function(a, ..., b) 1 +function(a = 1, ..., b = 2) a +function(..1, ..2) get("..1") + get("..2") +function(..1 = 1, ..2 = 2) get("..1") + get("..2") +function( + x = # important! + 4 +) 1 + +\(x, y) 1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@9..10 "a" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@10..12 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@12..13 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@13..22 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@22..23 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@23..24 "a" [] [], + }, + default: missing (optional), + }, + COMMA@24..26 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@26..27 "b" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@27..29 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@29..31 "a" [] [Whitespace(" ")], + }, + operator: PLUS@31..33 "+" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@33..34 "b" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@34..43 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@43..44 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@44..61 "a" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# important!")], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@61..64 ")" [Newline("\n")] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@64..65 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@65..74 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@74..75 "(" [] [], + items: RParameterList [ + RParameter { + name: RDots { + value_token: DOTS@75..78 "..." [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@78..80 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@80..81 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@81..90 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@90..91 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@91..92 "a" [] [], + }, + default: missing (optional), + }, + COMMA@92..94 "," [] [Whitespace(" ")], + RParameter { + name: RDots { + value_token: DOTS@94..97 "..." [] [], + }, + default: missing (optional), + }, + COMMA@97..99 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@99..100 "b" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@100..102 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@102..103 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@103..112 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@112..113 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@113..115 "a" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@115..117 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@117..118 "1" [] [], + }, + }, + }, + COMMA@118..120 "," [] [Whitespace(" ")], + RParameter { + name: RDots { + value_token: DOTS@120..123 "..." [] [], + }, + default: missing (optional), + }, + COMMA@123..125 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@125..127 "b" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@127..129 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@129..130 "2" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@130..132 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@132..133 "a" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@133..142 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@142..143 "(" [] [], + items: RParameterList [ + RParameter { + name: RDotDotI { + value_token: DOTDOTI@143..146 "..1" [] [], + }, + default: missing (optional), + }, + COMMA@146..148 "," [] [Whitespace(" ")], + RParameter { + name: RDotDotI { + value_token: DOTDOTI@148..151 "..2" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@151..153 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RCall { + function: RIdentifier { + name_token: IDENT@153..156 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@156..157 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@157..162 "\"..1\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@162..164 ")" [] [Whitespace(" ")], + }, + }, + operator: PLUS@164..166 "+" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@166..169 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@169..170 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@170..175 "\"..2\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@175..176 ")" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@176..185 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@185..186 "(" [] [], + items: RParameterList [ + RParameter { + name: RDotDotI { + value_token: DOTDOTI@186..190 "..1" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@190..192 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@192..193 "1" [] [], + }, + }, + }, + COMMA@193..195 "," [] [Whitespace(" ")], + RParameter { + name: RDotDotI { + value_token: DOTDOTI@195..199 "..2" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@199..201 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@201..202 "2" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@202..204 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RCall { + function: RIdentifier { + name_token: IDENT@204..207 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@207..208 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@208..213 "\"..1\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@213..215 ")" [] [Whitespace(" ")], + }, + }, + operator: PLUS@215..217 "+" [] [Whitespace(" ")], + right: RCall { + function: RIdentifier { + name_token: IDENT@217..220 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@220..221 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@221..226 "\"..2\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@226..227 ")" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@227..236 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@236..237 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@237..242 "x" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@242..256 "=" [] [Whitespace(" "), Comments("# important!")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@256..260 "4" [Newline("\n"), Whitespace(" ")] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@260..263 ")" [Newline("\n")] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@263..264 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@264..267 "\\" [Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@267..268 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@268..269 "x" [] [], + }, + default: missing (optional), + }, + COMMA@269..271 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@271..272 "y" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@272..274 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@274..275 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@275..276 "" [Newline("\n")] [], +} +``` + +## A body stops at the binary `?` + +Weirdly in R, help is precedence 1 and function and loop bodies are precedence 2. +So `function() x ? y` must parse as `(function () x) ? y`. + +```r +function() a ? b +function() a ? b ? c +?function() a +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@9..11 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@11..13 "a" [] [Whitespace(" ")], + }, + }, + operator: WAT@13..15 "?" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@15..16 "b" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RFunctionDefinition { + name: FUNCTION_KW@16..25 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@25..26 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@26..28 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@28..30 "a" [] [Whitespace(" ")], + }, + }, + operator: WAT@30..32 "?" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@32..34 "b" [] [Whitespace(" ")], + }, + }, + operator: WAT@34..36 "?" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@36..37 "c" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RUnaryExpression { + operator: WAT@37..39 "?" [Newline("\n")] [], + argument: RFunctionDefinition { + name: FUNCTION_KW@39..47 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@47..48 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@48..50 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@50..51 "a" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@51..52 "" [Newline("\n")] [], +} +``` + +```r +function() a <- b +function() a = b +1 + function(x) x + 2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@9..11 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@11..13 "a" [] [Whitespace(" ")], + }, + operator: ASSIGN@13..16 "<-" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@16..17 "b" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@17..26 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@26..27 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@27..29 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@29..31 "a" [] [Whitespace(" ")], + }, + operator: EQUAL@31..33 "=" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@33..34 "b" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@34..37 "1" [Newline("\n")] [Whitespace(" ")], + }, + operator: PLUS@37..39 "+" [] [Whitespace(" ")], + right: RFunctionDefinition { + name: FUNCTION_KW@39..47 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@47..48 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@48..49 "x" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@49..51 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@51..53 "x" [] [Whitespace(" ")], + }, + operator: PLUS@53..55 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@55..56 "2" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@56..57 "" [Newline("\n")] [], +} +``` + +## A definition with no parameter list [error] + +```r +function 1 +\ 1 +function +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..9 "function" [] [Whitespace(" ")], + parameters: missing (required), + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@9..10 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@10..13 "\\" [Newline("\n")] [Whitespace(" ")], + parameters: missing (required), + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@13..14 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@14..23 "function" [Newline("\n")] [], + parameters: missing (required), + body: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@23..24 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter list but instead found '1'. + + > 1 │ function 1 + │ ^ + 2 │ \ 1 + 3 │ function + +test.R:2:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter list but instead found '1'. + + 1 │ function 1 + > 2 │ \ 1 + │ ^ + 3 │ function + 4 │ + +test.R:4:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter list but instead found the end of the file. + + 2 │ \ 1 + 3 │ function + > 4 │ + │ +``` diff --git a/crates/air_r_parser_tests/tests/corpus/functions/lambda.md b/crates/air_r_parser_tests/tests/corpus/functions/lambda.md new file mode 100644 index 00000000..50ad688f --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/functions/lambda.md @@ -0,0 +1,18 @@ +## Lambda function + +```r +a <- \(arg) arg +b <- \(arg1, arg2) paste0(arg1, arg2) +c <- \(fun, ...) fun(...) +1:3 |> {\(x, y = 1) x + y}() |> {\(x) sum(x)}() +{\(a = 1) a + 1}() +\() 1 + 2 +\() + + 1 + 2 + +# Not currently allowed by R's parser, but we think it will be and is just an oversight. +# `'\\'` would need to be included alongside `FUNCTION` here https://github.com/wch/r-source/blob/802121c877837926a6bc2a930b3da749b537258b/src/main/gram.y#L3898-L3901. +\ +() 1 + 1 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/functions/lambda.snap.md b/crates/air_r_parser_tests/tests/corpus/functions/lambda.snap.md new file mode 100644 index 00000000..4aa9d302 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/functions/lambda.snap.md @@ -0,0 +1,385 @@ + + +## Lambda function + +```r +a <- \(arg) arg +b <- \(arg1, arg2) paste0(arg1, arg2) +c <- \(fun, ...) fun(...) +1:3 |> {\(x, y = 1) x + y}() |> {\(x) sum(x)}() +{\(a = 1) a + 1}() +\() 1 + 2 +\() + + 1 + 2 + +# Not currently allowed by R's parser, but we think it will be and is just an oversight. +# `'\\'` would need to be included alongside `FUNCTION` here https://github.com/wch/r-source/blob/802121c877837926a6bc2a930b3da749b537258b/src/main/gram.y#L3898-L3901. +\ +() 1 + 1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "a" [] [Whitespace(" ")], + }, + operator: ASSIGN@2..5 "<-" [] [Whitespace(" ")], + right: RFunctionDefinition { + name: BACKSLASH@5..6 "\\" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@6..7 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@7..10 "arg" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@10..12 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@12..15 "arg" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@15..18 "b" [Newline("\n")] [Whitespace(" ")], + }, + operator: ASSIGN@18..21 "<-" [] [Whitespace(" ")], + right: RFunctionDefinition { + name: BACKSLASH@21..22 "\\" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@22..23 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@23..27 "arg1" [] [], + }, + default: missing (optional), + }, + COMMA@27..29 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@29..33 "arg2" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@33..35 ")" [] [Whitespace(" ")], + }, + body: RCall { + function: RIdentifier { + name_token: IDENT@35..41 "paste0" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@41..42 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@42..46 "arg1" [] [], + }, + }, + COMMA@46..48 "," [] [Whitespace(" ")], + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@48..52 "arg2" [] [], + }, + }, + ], + r_paren_token: R_PAREN@52..53 ")" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@53..56 "c" [Newline("\n")] [Whitespace(" ")], + }, + operator: ASSIGN@56..59 "<-" [] [Whitespace(" ")], + right: RFunctionDefinition { + name: BACKSLASH@59..60 "\\" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@60..61 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@61..64 "fun" [] [], + }, + default: missing (optional), + }, + COMMA@64..66 "," [] [Whitespace(" ")], + RParameter { + name: RDots { + value_token: DOTS@66..69 "..." [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@69..71 ")" [] [Whitespace(" ")], + }, + body: RCall { + function: RIdentifier { + name_token: IDENT@71..74 "fun" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@74..75 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDots { + value_token: DOTS@75..78 "..." [] [], + }, + }, + ], + r_paren_token: R_PAREN@78..79 ")" [] [], + }, + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RBinaryExpression { + left: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@79..81 "1" [Newline("\n")] [], + }, + operator: COLON@81..82 ":" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@82..84 "3" [] [Whitespace(" ")], + }, + }, + operator: PIPE@84..87 "|>" [] [Whitespace(" ")], + right: RCall { + function: RBracedStatements { + l_curly_token: L_CURLY@87..88 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@88..89 "\\" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@89..90 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@90..91 "x" [] [], + }, + default: missing (optional), + }, + COMMA@91..93 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@93..95 "y" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@95..97 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@97..98 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@98..100 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@100..102 "x" [] [Whitespace(" ")], + }, + operator: PLUS@102..104 "+" [] [Whitespace(" ")], + right: RIdentifier { + name_token: IDENT@104..105 "y" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@105..106 "}" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@106..107 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@107..109 ")" [] [Whitespace(" ")], + }, + }, + }, + operator: PIPE@109..112 "|>" [] [Whitespace(" ")], + right: RCall { + function: RBracedStatements { + l_curly_token: L_CURLY@112..113 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@113..114 "\\" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@114..115 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@115..116 "x" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@116..118 ")" [] [Whitespace(" ")], + }, + body: RCall { + function: RIdentifier { + name_token: IDENT@118..121 "sum" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@121..122 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@122..123 "x" [] [], + }, + }, + ], + r_paren_token: R_PAREN@123..124 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@124..125 "}" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@125..126 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@126..127 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RBracedStatements { + l_curly_token: L_CURLY@127..129 "{" [Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@129..130 "\\" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@130..131 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@131..133 "a" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@133..135 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@135..136 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@136..138 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@138..140 "a" [] [Whitespace(" ")], + }, + operator: PLUS@140..142 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@142..143 "1" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@143..144 "}" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@144..145 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@145..146 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@146..148 "\\" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@148..149 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@149..151 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@151..153 "1" [] [Whitespace(" ")], + }, + operator: PLUS@153..155 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@155..156 "2" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@156..158 "\\" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@158..159 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@159..160 ")" [] [], + }, + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@160..166 "1" [Newline("\n"), Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: PLUS@166..168 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@168..169 "2" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@169..429 "\\" [Newline("\n"), Newline("\n"), Comments("# Not currently allow ..."), Newline("\n"), Comments("# `'\\\\'` would need t ..."), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@429..431 "(" [Newline("\n")] [], + items: RParameterList [], + r_paren_token: R_PAREN@431..433 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@433..435 "1" [] [Whitespace(" ")], + }, + operator: PLUS@435..437 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@437..438 "1" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@438..439 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/functions/parameters.md b/crates/air_r_parser_tests/tests/corpus/functions/parameters.md new file mode 100644 index 00000000..0b517779 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/functions/parameters.md @@ -0,0 +1,91 @@ +## Functions with `return` as parameter name + +```r +# `return` is not a reserved word in R (it's not in `?Reserved`), so it is +# allowed as a parameter name (r-lib/tree-sitter-r#189). + +function(return) return +function(return = 1) return +``` + +## Function dots and dot dot i + +```r +# Dots as parameter without default +function(...) { list(...) } + +# Dots as parameter with default +function(... = 1) { get("...") } + +# `..i` as parameter without default +function(..1) { get("..1") } + +# `..i` as parameter with default +function(..1 = 1) { get("..1") } + +# Miscellaneous +function(x, ...) f(...) +function(x, ...) ..1 + ..2 +``` + +## A hole in a parameter list [error] + +```r +function(a,,b) 1 +function(a,) 1 +function(,) 1 +``` + +## A parameter that is not a name [error] + +```r +function(1) x +function("a") x +function(a$b) x +``` + +Recovery stops at the `=`, so a bad name costs the name and nothing else. + +```r +function(1 = 2) x +function("a" = 1) x +function(a, 1 = 2, b) x +function(= 1) x +``` + +## A missing comma between parameters [error] + +A name is a single token, so the token after one may well be another parameter. +Reporting the separator without consuming anything keeps both potential parameters. + +```r +function(a b) x +``` + +## A default with no value [error] + +The default is required once an `=` is written (r-lib/tree-sitter-r#161). + +```r +function(x = ) 1 +``` + +## A parameter list with no closing parenthesis [error] + +The list runs to the end of the file and the `)` is reported missing, leaving +`RParameters` and the definition around it whole. + +```r +function(a +``` + +```r +\(x +``` + +A closer that belongs to someone else ends the list without being consumed + +```r +function(a} 1 +function(a] 1 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/functions/parameters.snap.md b/crates/air_r_parser_tests/tests/corpus/functions/parameters.snap.md new file mode 100644 index 00000000..c667afac --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/functions/parameters.snap.md @@ -0,0 +1,1114 @@ + + +## Functions with `return` as parameter name + +```r +# `return` is not a reserved word in R (it's not in `?Reserved`), so it is +# allowed as a parameter name (r-lib/tree-sitter-r#189). + +function(return) return +function(return = 1) return +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..141 "function" [Comments("# `return` is not a r ..."), Newline("\n"), Comments("# allowed as a parame ..."), Newline("\n"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@141..142 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@142..148 "return" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@148..150 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@150..156 "return" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@156..165 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@165..166 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@166..173 "return" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@173..175 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@175..176 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@176..178 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@178..184 "return" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@184..185 "" [Newline("\n")] [], +} +``` + +## Function dots and dot dot i + +```r +# Dots as parameter without default +function(...) { list(...) } + +# Dots as parameter with default +function(... = 1) { get("...") } + +# `..i` as parameter without default +function(..1) { get("..1") } + +# `..i` as parameter with default +function(..1 = 1) { get("..1") } + +# Miscellaneous +function(x, ...) f(...) +function(x, ...) ..1 + ..2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..44 "function" [Comments("# Dots as parameter w ..."), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@44..45 "(" [] [], + items: RParameterList [ + RParameter { + name: RDots { + value_token: DOTS@45..48 "..." [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@48..50 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@50..52 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@52..56 "list" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@56..57 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDots { + value_token: DOTS@57..60 "..." [] [], + }, + }, + ], + r_paren_token: R_PAREN@60..62 ")" [] [Whitespace(" ")], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@62..63 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@63..106 "function" [Newline("\n"), Newline("\n"), Comments("# Dots as parameter w ..."), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@106..107 "(" [] [], + items: RParameterList [ + RParameter { + name: RDots { + value_token: DOTS@107..111 "..." [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@111..113 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@113..114 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@114..116 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@116..118 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@118..121 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@121..122 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@122..127 "\"...\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@127..129 ")" [] [Whitespace(" ")], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@129..130 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@130..177 "function" [Newline("\n"), Newline("\n"), Comments("# `..i` as parameter ..."), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@177..178 "(" [] [], + items: RParameterList [ + RParameter { + name: RDotDotI { + value_token: DOTDOTI@178..181 "..1" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@181..183 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@183..185 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@185..188 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@188..189 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@189..194 "\"..1\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@194..196 ")" [] [Whitespace(" ")], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@196..197 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@197..241 "function" [Newline("\n"), Newline("\n"), Comments("# `..i` as parameter ..."), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@241..242 "(" [] [], + items: RParameterList [ + RParameter { + name: RDotDotI { + value_token: DOTDOTI@242..246 "..1" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@246..248 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@248..249 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@249..251 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@251..253 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@253..256 "get" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@256..257 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@257..262 "\"..1\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@262..264 ")" [] [Whitespace(" ")], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@264..265 "}" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@265..291 "function" [Newline("\n"), Newline("\n"), Comments("# Miscellaneous"), Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@291..292 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@292..293 "x" [] [], + }, + default: missing (optional), + }, + COMMA@293..295 "," [] [Whitespace(" ")], + RParameter { + name: RDots { + value_token: DOTS@295..298 "..." [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@298..300 ")" [] [Whitespace(" ")], + }, + body: RCall { + function: RIdentifier { + name_token: IDENT@300..301 "f" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@301..302 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDots { + value_token: DOTS@302..305 "..." [] [], + }, + }, + ], + r_paren_token: R_PAREN@305..306 ")" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@306..315 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@315..316 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@316..317 "x" [] [], + }, + default: missing (optional), + }, + COMMA@317..319 "," [] [Whitespace(" ")], + RParameter { + name: RDots { + value_token: DOTS@319..322 "..." [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@322..324 ")" [] [Whitespace(" ")], + }, + body: RBinaryExpression { + left: RDotDotI { + value_token: DOTDOTI@324..328 "..1" [] [Whitespace(" ")], + }, + operator: PLUS@328..330 "+" [] [Whitespace(" ")], + right: RDotDotI { + value_token: DOTDOTI@330..333 "..2" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@333..334 "" [Newline("\n")] [], +} +``` + +## A hole in a parameter list [error] + +```r +function(a,,b) 1 +function(a,) 1 +function(,) 1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@9..10 "a" [] [], + }, + default: missing (optional), + }, + COMMA@10..11 "," [] [], + RParameter { + name: missing (required), + default: missing (optional), + }, + COMMA@11..12 "," [] [], + RParameter { + name: RIdentifier { + name_token: IDENT@12..13 "b" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@13..15 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@15..16 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@16..25 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@25..26 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@26..27 "a" [] [], + }, + default: missing (optional), + }, + COMMA@27..28 "," [] [], + RParameter { + name: missing (required), + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@28..30 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@30..31 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@31..40 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@40..41 "(" [] [], + items: RParameterList [ + RParameter { + name: missing (required), + default: missing (optional), + }, + COMMA@41..42 "," [] [], + RParameter { + name: missing (required), + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@42..44 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@44..45 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@45..46 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:12 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found ','. + + > 1 │ function(a,,b) 1 + │ ^ + 2 │ function(a,) 1 + 3 │ function(,) 1 + +test.R:2:12 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found ')'. + + 1 │ function(a,,b) 1 + > 2 │ function(a,) 1 + │ ^ + 3 │ function(,) 1 + 4 │ + +test.R:3:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found ','. + + 1 │ function(a,,b) 1 + 2 │ function(a,) 1 + > 3 │ function(,) 1 + │ ^ + 4 │ + +test.R:3:11 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found ')'. + + 1 │ function(a,,b) 1 + 2 │ function(a,) 1 + > 3 │ function(,) 1 + │ ^ + 4 │ +``` + +## A parameter that is not a name [error] + +```r +function(1) x +function("a") x +function(a$b) x +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RBogusParameterName { + items: [ + R_DOUBLE_LITERAL@9..10 "1" [] [], + ], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@10..12 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@12..13 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@13..22 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@22..23 "(" [] [], + items: RParameterList [ + RParameter { + name: RBogusParameterName { + items: [ + R_DOUBLE_QUOTED_STRING_LITERAL@23..26 "\"a\"" [] [], + ], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@26..28 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@28..29 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@29..38 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@38..39 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@39..40 "a" [] [], + }, + default: missing (optional), + }, + missing separator, + RParameter { + name: RBogusParameterName { + items: [ + DOLLAR@40..41 "$" [] [], + IDENT@41..42 "b" [] [], + ], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@42..44 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@44..45 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@45..46 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found '1'. + + > 1 │ function(1) x + │ ^ + 2 │ function("a") x + 3 │ function(a$b) x + +test.R:2:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found '"a"'. + + 1 │ function(1) x + > 2 │ function("a") x + │ ^^^ + 3 │ function(a$b) x + 4 │ + +test.R:3:11 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `$` + + 1 │ function(1) x + 2 │ function("a") x + > 3 │ function(a$b) x + │ ^ + 4 │ + + i Remove $ +``` + +Recovery stops at the `=`, so a bad name costs the name and nothing else. + +```r +function(1 = 2) x +function("a" = 1) x +function(a, 1 = 2, b) x +function(= 1) x +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RBogusParameterName { + items: [ + R_DOUBLE_LITERAL@9..11 "1" [] [Whitespace(" ")], + ], + }, + default: RParameterDefault { + eq_token: EQUAL@11..13 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@13..14 "2" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@14..16 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@16..17 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@17..26 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@26..27 "(" [] [], + items: RParameterList [ + RParameter { + name: RBogusParameterName { + items: [ + R_DOUBLE_QUOTED_STRING_LITERAL@27..31 "\"a\"" [] [Whitespace(" ")], + ], + }, + default: RParameterDefault { + eq_token: EQUAL@31..33 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@33..34 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@34..36 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@36..37 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@37..46 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@46..47 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@47..48 "a" [] [], + }, + default: missing (optional), + }, + COMMA@48..50 "," [] [Whitespace(" ")], + RParameter { + name: RBogusParameterName { + items: [ + R_DOUBLE_LITERAL@50..52 "1" [] [Whitespace(" ")], + ], + }, + default: RParameterDefault { + eq_token: EQUAL@52..54 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@54..55 "2" [] [], + }, + }, + }, + COMMA@55..57 "," [] [Whitespace(" ")], + RParameter { + name: RIdentifier { + name_token: IDENT@57..58 "b" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@58..60 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@60..61 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@61..70 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@70..71 "(" [] [], + items: RParameterList [ + RParameter { + name: missing (required), + default: RParameterDefault { + eq_token: EQUAL@71..73 "=" [] [Whitespace(" ")], + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@73..74 "1" [] [], + }, + }, + }, + ], + r_paren_token: R_PAREN@74..76 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@76..77 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@77..78 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found '1'. + + > 1 │ function(1 = 2) x + │ ^ + 2 │ function("a" = 1) x + 3 │ function(a, 1 = 2, b) x + +test.R:2:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found '"a"'. + + 1 │ function(1 = 2) x + > 2 │ function("a" = 1) x + │ ^^^ + 3 │ function(a, 1 = 2, b) x + 4 │ function(= 1) x + +test.R:3:13 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found '1'. + + 1 │ function(1 = 2) x + 2 │ function("a" = 1) x + > 3 │ function(a, 1 = 2, b) x + │ ^ + 4 │ function(= 1) x + 5 │ + +test.R:4:10 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a parameter name but instead found '='. + + 2 │ function("a" = 1) x + 3 │ function(a, 1 = 2, b) x + > 4 │ function(= 1) x + │ ^ + 5 │ +``` + +## A missing comma between parameters [error] + +A name is a single token, so the token after one may well be another parameter. +Reporting the separator without consuming anything keeps both potential parameters. + +```r +function(a b) x +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@9..11 "a" [] [Whitespace(" ")], + }, + default: missing (optional), + }, + missing separator, + RParameter { + name: RIdentifier { + name_token: IDENT@11..12 "b" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@12..14 ")" [] [Whitespace(" ")], + }, + body: RIdentifier { + name_token: IDENT@14..15 "x" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@15..16 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:12 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `b` + + > 1 │ function(a b) x + │ ^ + 2 │ + + i Remove b +``` + +## A default with no value [error] + +The default is required once an `=` is written (r-lib/tree-sitter-r#161). + +```r +function(x = ) 1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@9..11 "x" [] [Whitespace(" ")], + }, + default: RParameterDefault { + eq_token: EQUAL@11..13 "=" [] [Whitespace(" ")], + value: missing (required), + }, + }, + ], + r_paren_token: R_PAREN@13..15 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@15..16 "1" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@16..17 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:14 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ')'. + + > 1 │ function(x = ) 1 + │ ^ + 2 │ +``` + +## A parameter list with no closing parenthesis [error] + +The list runs to the end of the file and the `)` is reported missing, leaving +`RParameters` and the definition around it whole. + +```r +function(a +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@9..10 "a" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: missing (required), + }, + body: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@10..11 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead the file ends + + 1 │ function(a + > 2 │ + │ + + i the file ends here + + 1 │ function(a + > 2 │ + │ +``` + +```r +\(x +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: BACKSLASH@0..1 "\\" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@2..3 "x" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: missing (required), + }, + body: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@3..4 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead the file ends + + 1 │ \(x + > 2 │ + │ + + i the file ends here + + 1 │ \(x + > 2 │ + │ +``` + +A closer that belongs to someone else ends the list without being consumed + +```r +function(a} 1 +function(a] 1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@9..10 "a" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: missing (required), + }, + body: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_CURLY@10..12 "}" [] [Whitespace(" ")], + R_DOUBLE_LITERAL@12..13 "1" [] [], + ], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@13..22 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@22..23 "(" [] [], + items: RParameterList [ + RParameter { + name: RIdentifier { + name_token: IDENT@23..24 "a" [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: missing (required), + }, + body: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_BRACK@24..26 "]" [] [Whitespace(" ")], + R_DOUBLE_LITERAL@26..27 "1" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@27..28 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:11 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `}` + + > 1 │ function(a} 1 + │ ^ + 2 │ function(a] 1 + 3 │ + + i Remove } + +test.R:2:11 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead found `]` + + 1 │ function(a} 1 + > 2 │ function(a] 1 + │ ^ + 3 │ + + i Remove ] +``` diff --git a/crates/air_r_parser_tests/tests/corpus/literals/comments.md b/crates/air_r_parser_tests/tests/corpus/literals/comments.md new file mode 100644 index 00000000..17ce57b8 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/literals/comments.md @@ -0,0 +1,26 @@ +## Comments + +```r +# a comment' + +'# not a comment' + + +' +# still not a comment' +``` + +## Leading and trailing comments + +```r +# Leading +1 + 1 # Trailing +# Leading +``` + +## Multi-byte Unicode in a comment + +```r +# héllo wörld 日本語 emoji 🎉 comment +1 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/literals/comments.snap.md b/crates/air_r_parser_tests/tests/corpus/literals/comments.snap.md new file mode 100644 index 00000000..62a11725 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/literals/comments.snap.md @@ -0,0 +1,85 @@ + + +## Comments + +```r +# a comment' + +'# not a comment' + + +' +# still not a comment' +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@0..31 "'# not a comment'" [Comments("# a comment'"), Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@31..58 "'\n# still not a comment'" [Newline("\n"), Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@58..59 "" [Newline("\n")] [], +} +``` + +## Leading and trailing comments + +```r +# Leading +1 + 1 # Trailing +# Leading +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..12 "1" [Comments("# Leading"), Newline("\n")] [Whitespace(" ")], + }, + operator: PLUS@12..14 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@14..26 "1" [] [Whitespace(" "), Comments("# Trailing")], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@26..37 "" [Newline("\n"), Comments("# Leading"), Newline("\n")] [], +} +``` + +## Multi-byte Unicode in a comment + +```r +# héllo wörld 日本語 emoji 🎉 comment +1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..46 "1" [Comments("# héllo wörld 日本 ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@46..47 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser/tests/snapshots/ok/keyword.R b/crates/air_r_parser_tests/tests/corpus/literals/constants.md similarity index 79% rename from crates/air_r_parser/tests/snapshots/ok/keyword.R rename to crates/air_r_parser_tests/tests/corpus/literals/constants.md index d2107707..c2da4714 100644 --- a/crates/air_r_parser/tests/snapshots/ok/keyword.R +++ b/crates/air_r_parser_tests/tests/corpus/literals/constants.md @@ -1,3 +1,6 @@ +## Keywords + +```r break next TRUE @@ -10,3 +13,4 @@ NA_integer_ NA_real_ NA_complex_ NA_character_ +``` diff --git a/crates/air_r_parser/tests/snapshots/ok/keyword.R.snap b/crates/air_r_parser_tests/tests/corpus/literals/constants.snap.md similarity index 59% rename from crates/air_r_parser/tests/snapshots/ok/keyword.R.snap rename to crates/air_r_parser_tests/tests/corpus/literals/constants.snap.md index 9edcbf29..f3644bb2 100644 --- a/crates/air_r_parser/tests/snapshots/ok/keyword.R.snap +++ b/crates/air_r_parser_tests/tests/corpus/literals/constants.snap.md @@ -1,10 +1,8 @@ ---- -source: crates/air_r_parser/tests/spec_test.rs -expression: snapshot ---- -## Input + -```R +## Keywords + +```r break next TRUE @@ -17,13 +15,9 @@ NA_integer_ NA_real_ NA_complex_ NA_character_ - ``` - -## AST - -``` +```cst RRoot { bom_token: missing (optional), statements: RStatementList [ @@ -103,61 +97,3 @@ RRoot { eof_token: EOF@84..85 "" [Newline("\n")] [], } ``` - -## CST - -``` -0: R_ROOT@0..85 - 0: (empty) - 1: R_STATEMENT_LIST@0..84 - 0: R_EXPRESSION_STATEMENT@0..5 - 0: R_BREAK_EXPRESSION@0..5 - 0: BREAK_KW@0..5 "break" [] [] - 1: (empty) - 1: R_EXPRESSION_STATEMENT@5..10 - 0: R_NEXT_EXPRESSION@5..10 - 0: NEXT_KW@5..10 "next" [Newline("\n")] [] - 1: (empty) - 2: R_EXPRESSION_STATEMENT@10..15 - 0: R_TRUE_EXPRESSION@10..15 - 0: TRUE_KW@10..15 "TRUE" [Newline("\n")] [] - 1: (empty) - 3: R_EXPRESSION_STATEMENT@15..21 - 0: R_FALSE_EXPRESSION@15..21 - 0: FALSE_KW@15..21 "FALSE" [Newline("\n")] [] - 1: (empty) - 4: R_EXPRESSION_STATEMENT@21..26 - 0: R_NULL_EXPRESSION@21..26 - 0: NULL_KW@21..26 "NULL" [Newline("\n")] [] - 1: (empty) - 5: R_EXPRESSION_STATEMENT@26..30 - 0: R_INF_EXPRESSION@26..30 - 0: INF_KW@26..30 "Inf" [Newline("\n")] [] - 1: (empty) - 6: R_EXPRESSION_STATEMENT@30..34 - 0: R_NAN_EXPRESSION@30..34 - 0: NAN_KW@30..34 "NaN" [Newline("\n")] [] - 1: (empty) - 7: R_EXPRESSION_STATEMENT@34..37 - 0: R_NA_EXPRESSION@34..37 - 0: NA_LOGICAL_KW@34..37 "NA" [Newline("\n")] [] - 1: (empty) - 8: R_EXPRESSION_STATEMENT@37..49 - 0: R_NA_EXPRESSION@37..49 - 0: NA_INTEGER_KW@37..49 "NA_integer_" [Newline("\n")] [] - 1: (empty) - 9: R_EXPRESSION_STATEMENT@49..58 - 0: R_NA_EXPRESSION@49..58 - 0: NA_DOUBLE_KW@49..58 "NA_real_" [Newline("\n")] [] - 1: (empty) - 10: R_EXPRESSION_STATEMENT@58..70 - 0: R_NA_EXPRESSION@58..70 - 0: NA_COMPLEX_KW@58..70 "NA_complex_" [Newline("\n")] [] - 1: (empty) - 11: R_EXPRESSION_STATEMENT@70..84 - 0: R_NA_EXPRESSION@70..84 - 0: NA_CHARACTER_KW@70..84 "NA_character_" [Newline("\n")] [] - 1: (empty) - 2: EOF@84..85 "" [Newline("\n")] [] - -``` diff --git a/crates/air_r_parser_tests/tests/corpus/literals/identifiers.md b/crates/air_r_parser_tests/tests/corpus/literals/identifiers.md new file mode 100644 index 00000000..10152dec --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/literals/identifiers.md @@ -0,0 +1,81 @@ +## Identifiers + +```r +foo +foo2 +foo.bar +.foo.bar +.__NAMESPACE__. +. +.. +._ +._. +foo_bar +`_foo` +`a "literal"` +`another +literal \` foo` +`backslash followed by newline \ +` +`\`` +# Pipe placeholder +_ +# Recognized as a single `_foo` identifier, even if invalid R code (r-lib/tree-sitter-r#71). +_foo +__foo +_foo_ +``` + +## Unicode identifiers + +```r +你好 +.你.好. +.你_好. +``` + +## Invalid identifiers [error] + +```r +# R does not allow identifiers to start with a `.` followed by a digit, as this +# would cause ambiguity with `.1L` and `.1i`. R itself rejects this as a syntax +# error, we currently parse this as `.1` followed by `foo`, just like we do with +# `1foo` (r-lib/tree-sitter-r#190). +.1foo +``` + +## Dot dot i + +```r +..1 +..10 +``` + +## Unclosed backtick [error] + +```r +`\` +``` + +## Dots + +```r +# Parses, but fails at runtime +... + +{ ... } + +# An actual base R function +...() +``` + +## Dot dot i in functions and calls + +```r +function(...) { + ..1 + ..2 +} + +# Parses as a function +..1() +``` diff --git a/crates/air_r_parser_tests/tests/corpus/literals/identifiers.snap.md b/crates/air_r_parser_tests/tests/corpus/literals/identifiers.snap.md new file mode 100644 index 00000000..e2e40564 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/literals/identifiers.snap.md @@ -0,0 +1,429 @@ + + +## Identifiers + +```r +foo +foo2 +foo.bar +.foo.bar +.__NAMESPACE__. +. +.. +._ +._. +foo_bar +`_foo` +`a "literal"` +`another +literal \` foo` +`backslash followed by newline \ +` +`\`` +# Pipe placeholder +_ +# Recognized as a single `_foo` identifier, even if invalid R code (r-lib/tree-sitter-r#71). +_foo +__foo +_foo_ +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@0..3 "foo" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@3..8 "foo2" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@8..16 "foo.bar" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@16..25 ".foo.bar" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@25..41 ".__NAMESPACE__." [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@41..43 "." [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@43..46 ".." [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@46..49 "._" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@49..53 "._." [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@53..61 "foo_bar" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@61..68 "`_foo`" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@68..82 "`a \"literal\"`" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@82..107 "`another\nliteral \\` foo`" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@107..142 "`backslash followed by newline \\\n`" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@142..147 "`\\``" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@147..168 "_" [Newline("\n"), Comments("# Pipe placeholder"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@168..266 "_foo" [Newline("\n"), Comments("# Recognized as a sin ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@266..272 "__foo" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@272..278 "_foo_" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@278..279 "" [Newline("\n")] [], +} +``` + +## Unicode identifiers + +```r +你好 +.你.好. +.你_好. +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@0..6 "你好" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@6..16 ".你.好." [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@16..26 ".你_好." [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@26..27 "" [Newline("\n")] [], +} +``` + +## Invalid identifiers [error] + +```r +# R does not allow identifiers to start with a `.` followed by a digit, as this +# would cause ambiguity with `.1L` and `.1i`. R itself rejects this as a syntax +# error, we currently parse this as `.1` followed by `foo`, just like we do with +# `1foo` (r-lib/tree-sitter-r#190). +.1foo +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..279 ".1" [Comments("# R does not allow id ..."), Newline("\n"), Comments("# would cause ambigui ..."), Newline("\n"), Comments("# error, we currently ..."), Newline("\n"), Comments("# `1foo` (r-lib/tree- ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@279..282 "foo" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@282..283 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:5:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a newline or a `;` between two expressions. + + 3 │ # error, we currently parse this as `.1` followed by `foo`, just like we do with + 4 │ # `1foo` (r-lib/tree-sitter-r#190). + > 5 │ .1foo + │ ^^^ + 6 │ + + i The first expression ends here. + + 3 │ # error, we currently parse this as `.1` followed by `foo`, just like we do with + 4 │ # `1foo` (r-lib/tree-sitter-r#190). + > 5 │ .1foo + │ ^^ + 6 │ +``` + +## Dot dot i + +```r +..1 +..10 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDotDotI { + value_token: DOTDOTI@0..3 "..1" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDotDotI { + value_token: DOTDOTI@3..8 "..10" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@8..9 "" [Newline("\n")] [], +} +``` + +## Unclosed backtick [error] + +```r +`\` +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + ERROR_TOKEN@0..4 "`\\`\n" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@4..4 "" [] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unterminated backtick-quoted name. + + > 1 │ `\` + │ ^ + 2 │ + + i Reached the end of the file looking for a closing `. + + 1 │ `\` + > 2 │ + │ +``` + +## Dots + +```r +# Parses, but fails at runtime +... + +{ ... } + +# An actual base R function +...() +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDots { + value_token: DOTS@0..34 "..." [Comments("# Parses, but fails a ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@34..38 "{" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RDots { + value_token: DOTS@38..42 "..." [] [Whitespace(" ")], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@42..43 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RDots { + value_token: DOTS@43..76 "..." [Newline("\n"), Newline("\n"), Comments("# An actual base R fu ..."), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@76..77 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@77..78 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@78..79 "" [Newline("\n")] [], +} +``` + +## Dot dot i in functions and calls + +```r +function(...) { + ..1 + ..2 +} + +# Parses as a function +..1() +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@0..8 "function" [] [], + parameters: RParameters { + l_paren_token: L_PAREN@8..9 "(" [] [], + items: RParameterList [ + RParameter { + name: RDots { + value_token: DOTS@9..12 "..." [] [], + }, + default: missing (optional), + }, + ], + r_paren_token: R_PAREN@12..14 ")" [] [Whitespace(" ")], + }, + body: RBracedStatements { + l_curly_token: L_CURLY@14..15 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RDotDotI { + value_token: DOTDOTI@15..22 "..1" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: PLUS@22..24 "+" [] [Whitespace(" ")], + right: RDotDotI { + value_token: DOTDOTI@24..27 "..2" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@27..29 "}" [Newline("\n")] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RDotDotI { + value_token: DOTDOTI@29..57 "..1" [Newline("\n"), Newline("\n"), Comments("# Parses as a function"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@57..58 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@58..59 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@59..60 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/literals/numbers.md b/crates/air_r_parser_tests/tests/corpus/literals/numbers.md new file mode 100644 index 00000000..868823ca --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/literals/numbers.md @@ -0,0 +1,131 @@ +## Integers + +```r +12332L +0L +12L +1e1L +1L +1e5L +# Technically, R parses these as floats with a warning, but for our purposes this is good enough +0.1L +# r-lib/tree-sitter-r#198 +.1L +``` + +## Floats + +```r +.66 +.11 +123.4123 +.1234 +x <- -.66 +``` + +## Hexadecimal + +```r +# `x` vs `X` +0x123 +0X123 + +# Numbers and letters +0xDEAD +0XDEAD +0x1f2F3 +0X1f2F3 + +# `p` vs `P` +0x0p0 +0x0P0 +0x0p123 +0x0P123 + +# `+` and `-` +0x0p+0 +0x0p-0 +0x0p+123 +0x0p-123 + +# Decimal point +0x. +0x1. +0x.1 +0x1.1 + +# Decimal point with exponent +0x.p1 +0x1.p1 +0x.1p1 +0x1.1p1 + +# As integers +0x123L +0X123L +0xDEADL +0XDEADL +0x1f2F3L +0X1f2F3L +0x0p0L +0x0P0L +0x0p123L +0x0P123L +0x0p+0L +0x0p-0L +0x0p+123L +0x0p-123L +0x.L +0x1.L +0x.1L +0x1.1L +0x.p1L +0x1.p1L +0x.1p1L +0x1.1p1L +``` + +## Scientific notation floats + +```r +1e322 +1e-3 +1e+3 +1.8e10 +1.e10 +1e10 +``` + +## Complex + +```r +1i +0i +1.1i +# r-lib/tree-sitter-r#198 +.1i +1e10i +.1e10i +0x1i +0x.1i +``` + +## Double values + +```r +1 +2.5 +1e6 +0x123F + +# r-lib/tree-sitter-r#198 +.1 +``` + +## Complex values + +```r +2.5i +1e6i +0x123Fi +``` diff --git a/crates/air_r_parser_tests/tests/corpus/literals/numbers.snap.md b/crates/air_r_parser_tests/tests/corpus/literals/numbers.snap.md new file mode 100644 index 00000000..0f006e14 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/literals/numbers.snap.md @@ -0,0 +1,679 @@ + + +## Integers + +```r +12332L +0L +12L +1e1L +1L +1e5L +# Technically, R parses these as floats with a warning, but for our purposes this is good enough +0.1L +# r-lib/tree-sitter-r#198 +.1L +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@0..6 "12332L" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@6..9 "0L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@9..13 "12L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@13..18 "1e1L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@18..21 "1L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@21..26 "1e5L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@26..128 "0.1L" [Newline("\n"), Comments("# Technically, R pars ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@128..158 ".1L" [Newline("\n"), Comments("# r-lib/tree-sitter-r ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@158..159 "" [Newline("\n")] [], +} +``` + +## Floats + +```r +.66 +.11 +123.4123 +.1234 +x <- -.66 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..3 ".66" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@3..7 ".11" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@7..16 "123.4123" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@16..22 ".1234" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@22..25 "x" [Newline("\n")] [Whitespace(" ")], + }, + operator: ASSIGN@25..28 "<-" [] [Whitespace(" ")], + right: RUnaryExpression { + operator: MINUS@28..29 "-" [] [], + argument: RDoubleValue { + value_token: R_DOUBLE_LITERAL@29..32 ".66" [] [], + }, + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@32..33 "" [Newline("\n")] [], +} +``` + +## Hexadecimal + +```r +# `x` vs `X` +0x123 +0X123 + +# Numbers and letters +0xDEAD +0XDEAD +0x1f2F3 +0X1f2F3 + +# `p` vs `P` +0x0p0 +0x0P0 +0x0p123 +0x0P123 + +# `+` and `-` +0x0p+0 +0x0p-0 +0x0p+123 +0x0p-123 + +# Decimal point +0x. +0x1. +0x.1 +0x1.1 + +# Decimal point with exponent +0x.p1 +0x1.p1 +0x.1p1 +0x1.1p1 + +# As integers +0x123L +0X123L +0xDEADL +0XDEADL +0x1f2F3L +0X1f2F3L +0x0p0L +0x0P0L +0x0p123L +0x0P123L +0x0p+0L +0x0p-0L +0x0p+123L +0x0p-123L +0x.L +0x1.L +0x.1L +0x1.1L +0x.p1L +0x1.p1L +0x.1p1L +0x1.1p1L +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..18 "0x123" [Comments("# `x` vs `X`"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@18..24 "0X123" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@24..54 "0xDEAD" [Newline("\n"), Newline("\n"), Comments("# Numbers and letters"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@54..61 "0XDEAD" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@61..69 "0x1f2F3" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@69..77 "0X1f2F3" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@77..97 "0x0p0" [Newline("\n"), Newline("\n"), Comments("# `p` vs `P`"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@97..103 "0x0P0" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@103..111 "0x0p123" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@111..119 "0x0P123" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@119..141 "0x0p+0" [Newline("\n"), Newline("\n"), Comments("# `+` and `-`"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@141..148 "0x0p-0" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@148..157 "0x0p+123" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@157..166 "0x0p-123" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@166..187 "0x." [Newline("\n"), Newline("\n"), Comments("# Decimal point"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@187..192 "0x1." [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@192..197 "0x.1" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@197..203 "0x1.1" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@203..240 "0x.p1" [Newline("\n"), Newline("\n"), Comments("# Decimal point with ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@240..247 "0x1.p1" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@247..254 "0x.1p1" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@254..262 "0x1.1p1" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@262..284 "0x123L" [Newline("\n"), Newline("\n"), Comments("# As integers"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@284..291 "0X123L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@291..299 "0xDEADL" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@299..307 "0XDEADL" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@307..316 "0x1f2F3L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@316..325 "0X1f2F3L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@325..332 "0x0p0L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@332..339 "0x0P0L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@339..348 "0x0p123L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@348..357 "0x0P123L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@357..365 "0x0p+0L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@365..373 "0x0p-0L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@373..383 "0x0p+123L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@383..393 "0x0p-123L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@393..398 "0x.L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@398..404 "0x1.L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@404..410 "0x.1L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@410..417 "0x1.1L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@417..424 "0x.p1L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@424..432 "0x1.p1L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@432..440 "0x.1p1L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RIntegerValue { + value_token: R_INTEGER_LITERAL@440..449 "0x1.1p1L" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@449..450 "" [Newline("\n")] [], +} +``` + +## Scientific notation floats + +```r +1e322 +1e-3 +1e+3 +1.8e10 +1.e10 +1e10 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..5 "1e322" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..10 "1e-3" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@10..15 "1e+3" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@15..22 "1.8e10" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@22..28 "1.e10" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@28..33 "1e10" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@33..34 "" [Newline("\n")] [], +} +``` + +## Complex + +```r +1i +0i +1.1i +# r-lib/tree-sitter-r#198 +.1i +1e10i +.1e10i +0x1i +0x.1i +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@0..2 "1i" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@2..5 "0i" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@5..10 "1.1i" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@10..40 ".1i" [Newline("\n"), Comments("# r-lib/tree-sitter-r ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@40..46 "1e10i" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@46..53 ".1e10i" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@53..58 "0x1i" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@58..64 "0x.1i" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@64..65 "" [Newline("\n")] [], +} +``` + +## Double values + +```r +1 +2.5 +1e6 +0x123F + +# r-lib/tree-sitter-r#198 +.1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..5 "2.5" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..9 "1e6" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@9..16 "0x123F" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@16..46 ".1" [Newline("\n"), Newline("\n"), Comments("# r-lib/tree-sitter-r ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@46..47 "" [Newline("\n")] [], +} +``` + +## Complex values + +```r +2.5i +1e6i +0x123Fi +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@0..4 "2.5i" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@4..9 "1e6i" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RComplexValue { + value_token: R_COMPLEX_LITERAL@9..17 "0x123Fi" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@17..18 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/literals/strings.md b/crates/air_r_parser_tests/tests/corpus/literals/strings.md new file mode 100644 index 00000000..1fa0c0ec --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/literals/strings.md @@ -0,0 +1,186 @@ +## Strings + +```r +"foo" +"foo +bar" +"#" +"," +"}" +'foo' +'foo +bar' +'#' +',' +'}' +``` + +## Strings without content + +```r +"" +'' +``` + +## Strings with escape sequences + +```r +"\\" +"\ " +"\n" +"\t" +"\r" + +"\0" +"\1" +"\7" +"\12" +"\123" +"\1234" + +"\x1" +"\x01" +"\x012" + +"\u1" +"\u01" +"\u001" +"\u0001" +"\u00011" + +"\u{1}" +"\u{01}" +"\u{001}" +"\u{0001}" + +"\U{1}" +"\U{01}" +"\U{001}" +"\U{0001}" +"\U{00001}" +"\U{000001}" +"\U{0000001}" +"\U{00000001}" + +"\U1" +"\U01" +"\U001" +"\U0001" +"\U00001" +"\U000001" +"\U0000001" +"\U00000001" +"\U000000011" + +"foo\"bar" +"foo\ " +"foo\ +" +"\"" +'\'' +``` + +## Raw strings + +```r +r"(raw string)" +R"{another raw string}" +R"--[yet another ]- raw string]--" + +r"(")" +r"("")" +r"(')" +r"('')" + +r"-(-)-" +r"-()-)-" +r"--()-")--" + +r"( () )" +r"(())" +r"( ())" +r"(() )" +r"-())-" +r"-(())-)-" + +r"(raw +string +)" + +# Empty raw strings +r"()" +r'()' +R"{}" +r"--[]--" + +# Leading whitespace and newlines after open +r"( hello)" +r"(\nhello)" +r"( +hello +)" +f(r"(\nhello\n)") +f(r"( +hello +)") + +# Single quoted raw strings +r'(a)' +r'(')' +r'(")' + +r() +``` + +## Raw strings with `|` + +R accepts `|` as a raw string delimiter, though `?Quotes` doesn't say so. + +```r +r"|raw string|" +R'|raw string|' +r"--|a|--" + +r"||" +r"|||" +r"|a|b|" +r"-|a|-|-" + +r"|(|" +r"(|)" +``` + +## Unclosed double quote [error] + +```r +"\" +``` + +## Unclosed single quote [error] + +```r +'\' +``` + +## Unclosed raw string [error] + +```r +r"( +" +``` + +```r +r"|" +``` + +## String values + +```r +"hi there!" +'hi there!' +"'" +"multiline +string" +r"("some raw string +business")" +``` diff --git a/crates/air_r_parser_tests/tests/corpus/literals/strings.snap.md b/crates/air_r_parser_tests/tests/corpus/literals/strings.snap.md new file mode 100644 index 00000000..882917b6 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/literals/strings.snap.md @@ -0,0 +1,1017 @@ + + +## Strings + +```r +"foo" +"foo +bar" +"#" +"," +"}" +'foo' +'foo +bar' +'#' +',' +'}' +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@0..5 "\"foo\"" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@5..15 "\"foo\nbar\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@15..19 "\"#\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@19..23 "\",\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@23..27 "\"}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@27..33 "'foo'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@33..43 "'foo\nbar'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@43..47 "'#'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@47..51 "','" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@51..55 "'}'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@55..56 "" [Newline("\n")] [], +} +``` + +## Strings without content + +```r +"" +'' +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@0..2 "\"\"" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@2..5 "''" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@5..6 "" [Newline("\n")] [], +} +``` + +## Strings with escape sequences + +```r +"\\" +"\ " +"\n" +"\t" +"\r" + +"\0" +"\1" +"\7" +"\12" +"\123" +"\1234" + +"\x1" +"\x01" +"\x012" + +"\u1" +"\u01" +"\u001" +"\u0001" +"\u00011" + +"\u{1}" +"\u{01}" +"\u{001}" +"\u{0001}" + +"\U{1}" +"\U{01}" +"\U{001}" +"\U{0001}" +"\U{00001}" +"\U{000001}" +"\U{0000001}" +"\U{00000001}" + +"\U1" +"\U01" +"\U001" +"\U0001" +"\U00001" +"\U000001" +"\U0000001" +"\U00000001" +"\U000000011" + +"foo\"bar" +"foo\ " +"foo\ +" +"\"" +'\'' +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@0..4 "\"\\\\\"" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@4..9 "\"\\ \"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@9..14 "\"\\n\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@14..19 "\"\\t\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@19..24 "\"\\r\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@24..30 "\"\\0\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@30..35 "\"\\1\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@35..40 "\"\\7\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@40..46 "\"\\12\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@46..53 "\"\\123\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@53..61 "\"\\1234\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@61..68 "\"\\x1\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@68..75 "\"\\x01\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@75..83 "\"\\x012\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@83..90 "\"\\u1\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@90..97 "\"\\u01\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@97..105 "\"\\u001\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@105..114 "\"\\u0001\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@114..124 "\"\\u00011\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@124..133 "\"\\u{1}\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@133..142 "\"\\u{01}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@142..152 "\"\\u{001}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@152..163 "\"\\u{0001}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@163..172 "\"\\U{1}\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@172..181 "\"\\U{01}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@181..191 "\"\\U{001}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@191..202 "\"\\U{0001}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@202..214 "\"\\U{00001}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@214..227 "\"\\U{000001}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@227..241 "\"\\U{0000001}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@241..256 "\"\\U{00000001}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@256..263 "\"\\U1\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@263..270 "\"\\U01\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@270..278 "\"\\U001\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@278..287 "\"\\U0001\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@287..297 "\"\\U00001\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@297..308 "\"\\U000001\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@308..320 "\"\\U0000001\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@320..333 "\"\\U00000001\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@333..347 "\"\\U000000011\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@347..359 "\"foo\\\"bar\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@359..367 "\"foo\\ \"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@367..375 "\"foo\\\n\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@375..380 "\"\\\"\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@380..385 "'\\''" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@385..386 "" [Newline("\n")] [], +} +``` + +## Raw strings + +```r +r"(raw string)" +R"{another raw string}" +R"--[yet another ]- raw string]--" + +r"(")" +r"("")" +r"(')" +r"('')" + +r"-(-)-" +r"-()-)-" +r"--()-")--" + +r"( () )" +r"(())" +r"( ())" +r"(() )" +r"-())-" +r"-(())-)-" + +r"(raw +string +)" + +# Empty raw strings +r"()" +r'()' +R"{}" +r"--[]--" + +# Leading whitespace and newlines after open +r"( hello)" +r"(\nhello)" +r"( +hello +)" +f(r"(\nhello\n)") +f(r"( +hello +)") + +# Single quoted raw strings +r'(a)' +r'(')' +r'(")' + +r() +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@0..15 "r\"(raw string)\"" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@15..39 "R\"{another raw string}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@39..74 "R\"--[yet another ]- raw string]--\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@74..82 "r\"(\")\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@82..90 "r\"(\"\")\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@90..97 "r\"(')\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@97..105 "r\"('')\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@105..115 "r\"-(-)-\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@115..125 "r\"-()-)-\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@125..138 "r\"--()-\")--\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@138..149 "r\"( () )\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@149..157 "r\"(())\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@157..166 "r\"( ())\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@166..175 "r\"(() )\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@175..184 "r\"-())-\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@184..196 "r\"-(())-)-\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@196..214 "r\"(raw\nstring\n)\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@214..241 "r\"()\"" [Newline("\n"), Newline("\n"), Comments("# Empty raw strings"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@241..247 "r'()'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@247..253 "R\"{}\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@253..263 "r\"--[]--\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@263..322 "r\"( hello)\"" [Newline("\n"), Newline("\n"), Comments("# Leading whitespace ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@322..335 "r\"(\\nhello)\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@335..348 "r\"(\nhello\n)\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@348..350 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@350..351 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_RAW_STRING_LITERAL@351..365 "r\"(\\nhello\\n)\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@365..366 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@366..368 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@368..369 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RStringValue { + value: R_RAW_STRING_LITERAL@369..381 "r\"(\nhello\n)\"" [] [], + }, + }, + ], + r_paren_token: R_PAREN@381..382 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@382..418 "r'(a)'" [Newline("\n"), Newline("\n"), Comments("# Single quoted raw s ..."), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@418..425 "r'(')'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@425..432 "r'(\")'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@432..435 "r" [Newline("\n"), Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@435..436 "(" [] [], + items: RArgumentList [], + r_paren_token: R_PAREN@436..437 ")" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@437..438 "" [Newline("\n")] [], +} +``` + +## Raw strings with `|` + +R accepts `|` as a raw string delimiter, though `?Quotes` doesn't say so. + +```r +r"|raw string|" +R'|raw string|' +r"--|a|--" + +r"||" +r"|||" +r"|a|b|" +r"-|a|-|-" + +r"|(|" +r"(|)" +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@0..15 "r\"|raw string|\"" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@15..31 "R'|raw string|'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@31..42 "r\"--|a|--\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@42..49 "r\"||\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@49..56 "r\"|||\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@56..65 "r\"|a|b|\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@65..76 "r\"-|a|-|-\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@76..84 "r\"|(|\"" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@84..91 "r\"(|)\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@91..92 "" [Newline("\n")] [], +} +``` + +## Unclosed double quote [error] + +```r +"\" +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + ERROR_TOKEN@0..4 "\"\\\"\n" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@4..4 "" [] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unterminated string. + + > 1 │ "\" + │ ^ + 2 │ + + i Reached the end of the file looking for a closing delimiter. + + 1 │ "\" + > 2 │ + │ +``` + +## Unclosed single quote [error] + +```r +'\' +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + ERROR_TOKEN@0..4 "'\\'\n" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@4..4 "" [] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unterminated string. + + > 1 │ '\' + │ ^ + 2 │ + + i Reached the end of the file looking for a closing delimiter. + + 1 │ '\' + > 2 │ + │ +``` + +## Unclosed raw string [error] + +```r +r"( +" +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + ERROR_TOKEN@0..6 "r\"(\n\"\n" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@6..6 "" [] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unterminated string. + + > 1 │ r"( + │ ^^^ + 2 │ " + 3 │ + + i Reached the end of the file looking for a closing delimiter. + + 1 │ r"( + 2 │ " + > 3 │ + │ +``` + +```r +r"|" +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + ERROR_TOKEN@0..5 "r\"|\"\n" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@5..5 "" [] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unterminated string. + + > 1 │ r"|" + │ ^^^ + 2 │ + + i Reached the end of the file looking for a closing delimiter. + + 1 │ r"|" + > 2 │ + │ +``` + +## String values + +```r +"hi there!" +'hi there!' +"'" +"multiline +string" +r"("some raw string +business")" +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@0..11 "\"hi there!\"" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_SINGLE_QUOTED_STRING_LITERAL@11..23 "'hi there!'" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@23..27 "\"'\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_DOUBLE_QUOTED_STRING_LITERAL@27..46 "\"multiline\nstring\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RStringValue { + value: R_RAW_STRING_LITERAL@46..78 "r\"(\"some raw string\nbusiness\")\"" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@78..79 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/recovery/unmatched-delimiters.md b/crates/air_r_parser_tests/tests/corpus/recovery/unmatched-delimiters.md new file mode 100644 index 00000000..673dd627 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/recovery/unmatched-delimiters.md @@ -0,0 +1,70 @@ +## Closing brace [error] + +```r +} +``` + +## Closing parenthesis [error] + +```r +) +``` + +## Closing bracket [error] + +```r +] +``` + +## Opening brace, closing parenthesis [error] + +```r +# Parenthesis is "not valid" so it isn't matched as a closer for the `{` +{) +``` + +## Opening parenthesis, closing brace [error] + +```r +(} +``` + +## Opening parenthesis, closing bracket [error] + +```r +(] +``` + +## Opening bracket2, unmatched closing bracket [error] + +```r +x[[2] +``` + +## Opening bracket and bracket2, unmatched closing bracket [error] + +```r +x[y[[2] +``` + +## Opening bracket2 and bracket, matched closing bracket [error] + +```r +x[[y[2] +``` + +## Opening bracket2 and bracket, unmatched closing bracket [error] + +```r +x[[y[2]] +``` + +## An argument name cut off by the end of the file [error] + +```r +f(a +``` + +```r +f(a= +``` diff --git a/crates/air_r_parser_tests/tests/corpus/recovery/unmatched-delimiters.snap.md b/crates/air_r_parser_tests/tests/corpus/recovery/unmatched-delimiters.snap.md new file mode 100644 index 00000000..28181d25 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/recovery/unmatched-delimiters.snap.md @@ -0,0 +1,595 @@ + + +## Closing brace [error] + +```r +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_CURLY@0..1 "}" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@1..2 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '}'. + + > 1 │ } + │ ^ + 2 │ +``` + +## Closing parenthesis [error] + +```r +) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_PAREN@0..1 ")" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@1..2 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ')'. + + > 1 │ ) + │ ^ + 2 │ +``` + +## Closing bracket [error] + +```r +] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_BRACK@0..1 "]" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@1..2 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ']'. + + > 1 │ ] + │ ^ + 2 │ +``` + +## Opening brace, closing parenthesis [error] + +```r +# Parenthesis is "not valid" so it isn't matched as a closer for the `{` +{) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..74 "{" [Comments("# Parenthesis is \"not ..."), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_PAREN@74..75 ")" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: missing (required), + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@75..76 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ')'. + + 1 │ # Parenthesis is "not valid" so it isn't matched as a closer for the `{` + > 2 │ {) + │ ^ + 3 │ + +test.R:3:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `}` but instead the file ends + + 1 │ # Parenthesis is "not valid" so it isn't matched as a closer for the `{` + 2 │ {) + > 3 │ + │ + + i the file ends here + + 1 │ # Parenthesis is "not valid" so it isn't matched as a closer for the `{` + 2 │ {) + > 3 │ + │ +``` + +## Opening parenthesis, closing brace [error] + +```r +(} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@0..1 "(" [] [], + body: missing (required), + r_paren_token: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_CURLY@1..2 "}" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@2..3 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found '}'. + + > 1 │ (} + │ ^ + 2 │ +``` + +## Opening parenthesis, closing bracket [error] + +```r +(] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@0..1 "(" [] [], + body: missing (required), + r_paren_token: missing (required), + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_BRACK@1..2 "]" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@2..3 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ']'. + + > 1 │ (] + │ ^ + 2 │ +``` + +## Opening bracket2, unmatched closing bracket [error] + +```r +x[[2] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@3..4 "2" [] [], + }, + }, + ], + r_brack2_token: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_BRACK@4..5 "]" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@5..6 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `]]` but instead found `]` + + > 1 │ x[[2] + │ ^ + 2 │ + + i Remove ] +``` + +## Opening bracket and bracket2, unmatched closing bracket [error] + +```r +x[y[[2] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@1..2 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset2 { + function: RIdentifier { + name_token: IDENT@2..3 "y" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@3..5 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..6 "2" [] [], + }, + }, + ], + r_brack2_token: missing (required), + }, + }, + }, + ], + r_brack_token: R_BRACK@6..7 "]" [] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `]]` but instead found `]` + + > 1 │ x[y[[2] + │ ^ + 2 │ + + i Remove ] +``` + +## Opening bracket2 and bracket, matched closing bracket [error] + +```r +x[[y[2] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset { + function: RIdentifier { + name_token: IDENT@3..4 "y" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@4..5 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..6 "2" [] [], + }, + }, + ], + r_brack_token: R_BRACK@6..7 "]" [] [], + }, + }, + }, + ], + r_brack2_token: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `]]` but instead the file ends + + 1 │ x[[y[2] + > 2 │ + │ + + i the file ends here + + 1 │ x[[y[2] + > 2 │ + │ +``` + +## Opening bracket2 and bracket, unmatched closing bracket [error] + +```r +x[[y[2]] +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RSubset2 { + function: RIdentifier { + name_token: IDENT@0..1 "x" [] [], + }, + arguments: RSubset2Arguments { + l_brack2_token: L_BRACK2@1..3 "[[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RSubset { + function: RIdentifier { + name_token: IDENT@3..4 "y" [] [], + }, + arguments: RSubsetArguments { + l_brack_token: L_BRACK@4..5 "[" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..6 "2" [] [], + }, + }, + ], + r_brack_token: R_BRACK@6..7 "]" [] [], + }, + }, + }, + ], + r_brack2_token: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBogusExpression { + items: [ + R_BRACK@7..8 "]" [] [], + ], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@8..9 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:8 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `]]` but instead found `]` + + > 1 │ x[[y[2]] + │ ^ + 2 │ + + i Remove ] +``` + +## An argument name cut off by the end of the file [error] + +```r +f(a +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..1 "f" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@2..3 "a" [] [], + }, + }, + ], + r_paren_token: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@3..4 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead the file ends + + 1 │ f(a + > 2 │ + │ + + i the file ends here + + 1 │ f(a + > 2 │ + │ +``` + +```r +f(a= +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@0..1 "f" [] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@1..2 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: RArgumentNameClause { + name: RIdentifier { + name_token: IDENT@2..3 "a" [] [], + }, + eq_token: EQUAL@3..4 "=" [] [], + }, + value: missing (optional), + }, + ], + r_paren_token: missing (required), + }, + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@4..5 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `)` but instead the file ends + + 1 │ f(a= + > 2 │ + │ + + i the file ends here + + 1 │ f(a= + > 2 │ + │ +``` diff --git a/crates/air_r_parser/tests/snapshots/ok/braced_expressions.R b/crates/air_r_parser_tests/tests/corpus/statements/braces.md similarity index 55% rename from crates/air_r_parser/tests/snapshots/ok/braced_expressions.R rename to crates/air_r_parser_tests/tests/corpus/statements/braces.md index e5fe4a66..ecb250ee 100644 --- a/crates/air_r_parser/tests/snapshots/ok/braced_expressions.R +++ b/crates/air_r_parser_tests/tests/corpus/statements/braces.md @@ -1,10 +1,27 @@ -{ } +## Braced expressions -{ +```r +{} + +{1} + +{1; 2} + +{1; +2} + +{1 +2 +} +{ +1 +2 } -{ 1 } +{ + +} { 1 @@ -17,3 +34,4 @@ { # comment2 1 # comment3 } # comment4 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/statements/braces.snap.md b/crates/air_r_parser_tests/tests/corpus/statements/braces.snap.md new file mode 100644 index 00000000..e3612fd8 --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/statements/braces.snap.md @@ -0,0 +1,217 @@ + + +## Braced expressions + +```r +{} + +{1} + +{1; 2} + +{1; +2} + +{1 +2 +} + +{ +1 +2 +} + +{ + +} + +{ + 1 + 2 + 2 + 3 + + 4 +} + +# comment1 +{ # comment2 + 1 # comment3 +} # comment4 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..1 "{" [] [], + statements: RStatementList [], + r_curly_token: R_CURLY@1..2 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@2..5 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..6 "1" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@6..7 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@7..10 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@10..11 "1" [] [], + }, + semicolon_token: SEMICOLON@11..13 ";" [] [Whitespace(" ")], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@13..14 "2" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@14..15 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@15..18 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@18..19 "1" [] [], + }, + semicolon_token: SEMICOLON@19..20 ";" [] [], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@20..22 "2" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@22..23 "}" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@23..26 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@26..27 "1" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@27..29 "2" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@29..31 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@31..34 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@34..36 "1" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@36..38 "2" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@38..40 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@40..43 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [], + r_curly_token: R_CURLY@43..46 "}" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@46..49 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@49..53 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@53..58 "2" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: PLUS@58..60 "+" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@60..61 "2" [] [], + }, + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBinaryExpression { + left: RDoubleValue { + value_token: R_DOUBLE_LITERAL@61..66 "3" [Newline("\n"), Whitespace(" ")] [Whitespace(" ")], + }, + operator: PLUS@66..67 "+" [] [], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@67..73 "4" [Newline("\n"), Whitespace(" ")] [], + }, + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@73..75 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@75..100 "{" [Newline("\n"), Newline("\n"), Comments("# comment1"), Newline("\n")] [Whitespace(" "), Comments("# comment2")], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@100..115 "1" [Newline("\n"), Whitespace(" ")] [Whitespace(" "), Comments("# comment3")], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@115..128 "}" [Newline("\n")] [Whitespace(" "), Comments("# comment4")], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@128..129 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/statements/newlines.md b/crates/air_r_parser_tests/tests/corpus/statements/newlines.md new file mode 100644 index 00000000..3801d11e --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/statements/newlines.md @@ -0,0 +1,37 @@ +## Newlines + +```r +apple +(banana) + +{ + apple + (banana) +} + +( + apple + (banana) +) +``` + +## Two expressions on one line [error] + +```r +1 2 +``` + +## Two expressions on one line inside braces [error] + +```r +{ 1 2 } +``` + +## A comment does not hide a line break + +Lexer's `after_line_break` flag is sticky across trivia + +```r +1 # c +2 +``` diff --git a/crates/air_r_parser_tests/tests/corpus/statements/newlines.snap.md b/crates/air_r_parser_tests/tests/corpus/statements/newlines.snap.md new file mode 100644 index 00000000..007e257f --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/statements/newlines.snap.md @@ -0,0 +1,217 @@ + + +## Newlines + +```r +apple +(banana) + +{ + apple + (banana) +} + +( + apple + (banana) +) +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@0..5 "apple" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@5..7 "(" [Newline("\n")] [], + body: RIdentifier { + name_token: IDENT@7..13 "banana" [] [], + }, + r_paren_token: R_PAREN@13..14 ")" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@14..17 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RIdentifier { + name_token: IDENT@17..25 "apple" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@25..29 "(" [Newline("\n"), Whitespace(" ")] [], + body: RIdentifier { + name_token: IDENT@29..35 "banana" [] [], + }, + r_paren_token: R_PAREN@35..36 ")" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@36..38 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@38..41 "(" [Newline("\n"), Newline("\n")] [], + body: RCall { + function: RIdentifier { + name_token: IDENT@41..49 "apple" [Newline("\n"), Whitespace(" ")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@49..53 "(" [Newline("\n"), Whitespace(" ")] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RIdentifier { + name_token: IDENT@53..59 "banana" [] [], + }, + }, + ], + r_paren_token: R_PAREN@59..60 ")" [] [], + }, + }, + r_paren_token: R_PAREN@60..62 ")" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@62..63 "" [Newline("\n")] [], +} +``` + +## Two expressions on one line [error] + +```r +1 2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..2 "1" [] [Whitespace(" ")], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@2..3 "2" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@3..4 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a newline or a `;` between two expressions. + + > 1 │ 1 2 + │ ^ + 2 │ + + i The first expression ends here. + + > 1 │ 1 2 + │ ^ + 2 │ +``` + +## Two expressions on one line inside braces [error] + +```r +{ 1 2 } +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..2 "{" [] [Whitespace(" ")], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@2..4 "1" [] [Whitespace(" ")], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@4..6 "2" [] [Whitespace(" ")], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@6..7 "}" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:5 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected a newline or a `;` between two expressions. + + > 1 │ { 1 2 } + │ ^ + 2 │ + + i The first expression ends here. + + > 1 │ { 1 2 } + │ ^ + 2 │ +``` + +## A comment does not hide a line break + +Lexer's `after_line_break` flag is sticky across trivia + +```r +1 # c +2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..5 "1" [] [Whitespace(" "), Comments("# c")], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..7 "2" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/corpus/statements/semicolons.md b/crates/air_r_parser_tests/tests/corpus/statements/semicolons.md new file mode 100644 index 00000000..d75f925b --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/statements/semicolons.md @@ -0,0 +1,134 @@ +## Semicolons + +```r +1;2 + +1 ; 2 + +{ + 1; +} + +{ + 1; 2 +} + +1;# comment +1; # comment +``` + +## Semicolon at start of file [error] + +```r +; +1 +``` + +## Semicolon at end of file + +```r +# posit-dev/air#238 +1; +``` + +## Semicolon on its own line at end of file [error] + +```r +1 + +; +``` + +## A semicolon with no expression is an error at top level [error] + +```r +; +``` + +```r +;; +``` + +```r +;1 +``` + +```r +1;; +``` + +```r +1;;2 +``` + +```r +1 +; +``` + +The comment has no expression to attach to, so it lands on the `;`. + +```r +;# comment +``` + +## A semicolon with no expression is allowed inside braces + +```r +{ + ; +} +``` + +```r +{ + ;; +} +``` + +```r +{ + ;1 +} +``` + +```r +{ + 1;; +} +``` + +```r +{ + 1 + ; +} +``` + +```r +{ + 1 + + ; +} +``` + +```r +{ + ;# comment +} +``` + +## A semicolon terminates any kind of statement + +```r +x <- 1; +f(1); +(1); +{1}; +if (TRUE) 1; +for (i in 1) i; +while (FALSE) 1; +repeat break; +function() 1; +``` diff --git a/crates/air_r_parser_tests/tests/corpus/statements/semicolons.snap.md b/crates/air_r_parser_tests/tests/corpus/statements/semicolons.snap.md new file mode 100644 index 00000000..354da76e --- /dev/null +++ b/crates/air_r_parser_tests/tests/corpus/statements/semicolons.snap.md @@ -0,0 +1,817 @@ + + +## Semicolons + +```r +1;2 + +1 ; 2 + +{ + 1; +} + +{ + 1; 2 +} + +1;# comment +1; # comment +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], + }, + semicolon_token: SEMICOLON@1..2 ";" [] [], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@2..3 "2" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@3..7 "1" [Newline("\n"), Newline("\n")] [Whitespace(" ")], + }, + semicolon_token: SEMICOLON@7..9 ";" [] [Whitespace(" ")], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@9..10 "2" [] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@10..13 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@13..19 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: SEMICOLON@19..20 ";" [] [], + }, + ], + r_curly_token: R_CURLY@20..22 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@22..25 "{" [Newline("\n"), Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@25..31 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: SEMICOLON@31..33 ";" [] [Whitespace(" ")], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@33..34 "2" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@34..36 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@36..39 "1" [Newline("\n"), Newline("\n")] [], + }, + semicolon_token: SEMICOLON@39..49 ";" [] [Comments("# comment")], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@49..51 "1" [Newline("\n")] [], + }, + semicolon_token: SEMICOLON@51..62 ";" [] [Whitespace(" "), Comments("# comment")], + }, + ], + eof_token: EOF@62..63 "" [Newline("\n")] [], +} +``` + +## Semicolon at start of file [error] + +```r +; +1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@0..1 ";" [] [], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..3 "1" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@3..4 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + > 1 │ ; + │ ^ + 2 │ 1 + 3 │ + + i At top level, a `;` must directly follow an expression. +``` + +## Semicolon at end of file + +```r +# posit-dev/air#238 +1; +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..21 "1" [Comments("# posit-dev/air#238"), Newline("\n")] [], + }, + semicolon_token: SEMICOLON@21..22 ";" [] [], + }, + ], + eof_token: EOF@22..23 "" [Newline("\n")] [], +} +``` + +## Semicolon on its own line at end of file [error] + +```r +1 + +; +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], + }, + semicolon_token: missing (optional), + }, + REmptyStatement { + semicolon_token: SEMICOLON@1..4 ";" [Newline("\n"), Newline("\n")] [], + }, + ], + eof_token: EOF@4..5 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:3:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + 1 │ 1 + 2 │ + > 3 │ ; + │ ^ + 4 │ + + i At top level, a `;` must directly follow an expression. +``` + +## A semicolon with no expression is an error at top level [error] + +```r +; +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@0..1 ";" [] [], + }, + ], + eof_token: EOF@1..2 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + > 1 │ ; + │ ^ + 2 │ + + i At top level, a `;` must directly follow an expression. +``` + +```r +;; +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@0..1 ";" [] [], + }, + REmptyStatement { + semicolon_token: SEMICOLON@1..2 ";" [] [], + }, + ], + eof_token: EOF@2..3 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + > 1 │ ;; + │ ^ + 2 │ + + i At top level, a `;` must directly follow an expression. + +test.R:1:2 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + > 1 │ ;; + │ ^ + 2 │ + + i At top level, a `;` must directly follow an expression. +``` + +```r +;1 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@0..1 ";" [] [], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..2 "1" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@2..3 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + > 1 │ ;1 + │ ^ + 2 │ + + i At top level, a `;` must directly follow an expression. +``` + +```r +1;; +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], + }, + semicolon_token: SEMICOLON@1..2 ";" [] [], + }, + REmptyStatement { + semicolon_token: SEMICOLON@2..3 ";" [] [], + }, + ], + eof_token: EOF@3..4 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + > 1 │ 1;; + │ ^ + 2 │ + + i At top level, a `;` must directly follow an expression. +``` + +```r +1;;2 +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], + }, + semicolon_token: SEMICOLON@1..2 ";" [] [], + }, + REmptyStatement { + semicolon_token: SEMICOLON@2..3 ";" [] [], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@3..4 "2" [] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@4..5 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:3 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + > 1 │ 1;;2 + │ ^ + 2 │ + + i At top level, a `;` must directly follow an expression. +``` + +```r +1 +; +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@0..1 "1" [] [], + }, + semicolon_token: missing (optional), + }, + REmptyStatement { + semicolon_token: SEMICOLON@1..3 ";" [Newline("\n")] [], + }, + ], + eof_token: EOF@3..4 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:2:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + 1 │ 1 + > 2 │ ; + │ ^ + 3 │ + + i At top level, a `;` must directly follow an expression. +``` + +The comment has no expression to attach to, so it lands on the `;`. + +```r +;# comment +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@0..10 ";" [] [Comments("# comment")], + }, + ], + eof_token: EOF@10..11 "" [Newline("\n")] [], +} +``` + +```diagnostics +test.R:1:1 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Unexpected `;`. + + > 1 │ ;# comment + │ ^ + 2 │ + + i At top level, a `;` must directly follow an expression. +``` + +## A semicolon with no expression is allowed inside braces + +```r +{ + ; +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..1 "{" [] [], + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@1..5 ";" [Newline("\n"), Whitespace(" ")] [], + }, + ], + r_curly_token: R_CURLY@5..7 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@7..8 "" [Newline("\n")] [], +} +``` + +```r +{ + ;; +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..1 "{" [] [], + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@1..5 ";" [Newline("\n"), Whitespace(" ")] [], + }, + REmptyStatement { + semicolon_token: SEMICOLON@5..6 ";" [] [], + }, + ], + r_curly_token: R_CURLY@6..8 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@8..9 "" [Newline("\n")] [], +} +``` + +```r +{ + ;1 +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..1 "{" [] [], + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@1..5 ";" [Newline("\n"), Whitespace(" ")] [], + }, + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..6 "1" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@6..8 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@8..9 "" [Newline("\n")] [], +} +``` + +```r +{ + 1;; +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..1 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..5 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: SEMICOLON@5..6 ";" [] [], + }, + REmptyStatement { + semicolon_token: SEMICOLON@6..7 ";" [] [], + }, + ], + r_curly_token: R_CURLY@7..9 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@9..10 "" [Newline("\n")] [], +} +``` + +```r +{ + 1 + ; +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..1 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..5 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + REmptyStatement { + semicolon_token: SEMICOLON@5..9 ";" [Newline("\n"), Whitespace(" ")] [], + }, + ], + r_curly_token: R_CURLY@9..11 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@11..12 "" [Newline("\n")] [], +} +``` + +```r +{ + 1 + + ; +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..1 "{" [] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@1..5 "1" [Newline("\n"), Whitespace(" ")] [], + }, + semicolon_token: missing (optional), + }, + REmptyStatement { + semicolon_token: SEMICOLON@5..10 ";" [Newline("\n"), Newline("\n"), Whitespace(" ")] [], + }, + ], + r_curly_token: R_CURLY@10..12 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@12..13 "" [Newline("\n")] [], +} +``` + +```r +{ + ;# comment +} +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@0..1 "{" [] [], + statements: RStatementList [ + REmptyStatement { + semicolon_token: SEMICOLON@1..14 ";" [Newline("\n"), Whitespace(" ")] [Comments("# comment")], + }, + ], + r_curly_token: R_CURLY@14..16 "}" [Newline("\n")] [], + }, + semicolon_token: missing (optional), + }, + ], + eof_token: EOF@16..17 "" [Newline("\n")] [], +} +``` + +## A semicolon terminates any kind of statement + +```r +x <- 1; +f(1); +(1); +{1}; +if (TRUE) 1; +for (i in 1) i; +while (FALSE) 1; +repeat break; +function() 1; +``` + +```cst +RRoot { + bom_token: missing (optional), + statements: RStatementList [ + RExpressionStatement { + expression: RBinaryExpression { + left: RIdentifier { + name_token: IDENT@0..2 "x" [] [Whitespace(" ")], + }, + operator: ASSIGN@2..5 "<-" [] [Whitespace(" ")], + right: RDoubleValue { + value_token: R_DOUBLE_LITERAL@5..6 "1" [] [], + }, + }, + semicolon_token: SEMICOLON@6..7 ";" [] [], + }, + RExpressionStatement { + expression: RCall { + function: RIdentifier { + name_token: IDENT@7..9 "f" [Newline("\n")] [], + }, + arguments: RCallArguments { + l_paren_token: L_PAREN@9..10 "(" [] [], + items: RArgumentList [ + RArgument { + name_clause: missing (optional), + value: RDoubleValue { + value_token: R_DOUBLE_LITERAL@10..11 "1" [] [], + }, + }, + ], + r_paren_token: R_PAREN@11..12 ")" [] [], + }, + }, + semicolon_token: SEMICOLON@12..13 ";" [] [], + }, + RExpressionStatement { + expression: RParenthesizedExpression { + l_paren_token: L_PAREN@13..15 "(" [Newline("\n")] [], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@15..16 "1" [] [], + }, + r_paren_token: R_PAREN@16..17 ")" [] [], + }, + semicolon_token: SEMICOLON@17..18 ";" [] [], + }, + RExpressionStatement { + expression: RBracedStatements { + l_curly_token: L_CURLY@18..20 "{" [Newline("\n")] [], + statements: RStatementList [ + RExpressionStatement { + expression: RDoubleValue { + value_token: R_DOUBLE_LITERAL@20..21 "1" [] [], + }, + semicolon_token: missing (optional), + }, + ], + r_curly_token: R_CURLY@21..22 "}" [] [], + }, + semicolon_token: SEMICOLON@22..23 ";" [] [], + }, + RExpressionStatement { + expression: RIfStatement { + if_token: IF_KW@23..27 "if" [Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@27..28 "(" [] [], + condition: RTrueExpression { + true_token: TRUE_KW@28..32 "TRUE" [] [], + }, + r_paren_token: R_PAREN@32..34 ")" [] [Whitespace(" ")], + consequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@34..35 "1" [] [], + }, + else_clause: missing (optional), + }, + semicolon_token: SEMICOLON@35..36 ";" [] [], + }, + RExpressionStatement { + expression: RForStatement { + for_token: FOR_KW@36..41 "for" [Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@41..42 "(" [] [], + variable: RIdentifier { + name_token: IDENT@42..44 "i" [] [Whitespace(" ")], + }, + in_token: IN_KW@44..47 "in" [] [Whitespace(" ")], + sequence: RDoubleValue { + value_token: R_DOUBLE_LITERAL@47..48 "1" [] [], + }, + r_paren_token: R_PAREN@48..50 ")" [] [Whitespace(" ")], + body: RIdentifier { + name_token: IDENT@50..51 "i" [] [], + }, + }, + semicolon_token: SEMICOLON@51..52 ";" [] [], + }, + RExpressionStatement { + expression: RWhileStatement { + while_token: WHILE_KW@52..59 "while" [Newline("\n")] [Whitespace(" ")], + l_paren_token: L_PAREN@59..60 "(" [] [], + condition: RFalseExpression { + false_token: FALSE_KW@60..65 "FALSE" [] [], + }, + r_paren_token: R_PAREN@65..67 ")" [] [Whitespace(" ")], + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@67..68 "1" [] [], + }, + }, + semicolon_token: SEMICOLON@68..69 ";" [] [], + }, + RExpressionStatement { + expression: RRepeatStatement { + repeat_token: REPEAT_KW@69..77 "repeat" [Newline("\n")] [Whitespace(" ")], + body: RBreakExpression { + break_token: BREAK_KW@77..82 "break" [] [], + }, + }, + semicolon_token: SEMICOLON@82..83 ";" [] [], + }, + RExpressionStatement { + expression: RFunctionDefinition { + name: FUNCTION_KW@83..92 "function" [Newline("\n")] [], + parameters: RParameters { + l_paren_token: L_PAREN@92..93 "(" [] [], + items: RParameterList [], + r_paren_token: R_PAREN@93..95 ")" [] [Whitespace(" ")], + }, + body: RDoubleValue { + value_token: R_DOUBLE_LITERAL@95..96 "1" [] [], + }, + }, + semicolon_token: SEMICOLON@96..97 ";" [] [], + }, + ], + eof_token: EOF@97..98 "" [Newline("\n")] [], +} +``` diff --git a/crates/air_r_parser_tests/tests/main.rs b/crates/air_r_parser_tests/tests/main.rs new file mode 100644 index 00000000..ae36c2c8 --- /dev/null +++ b/crates/air_r_parser_tests/tests/main.rs @@ -0,0 +1,6 @@ +//! The corpus. One `#[test]` per input file, so `nextest` filters read as +//! `corpus::expressions::extract`. + +mod corpus { + air_r_test_macros::gen_corpus_tests!("tests/corpus", air_r_parser_tests::run_file); +} diff --git a/crates/air_r_parser_tests/tests/quick_test.rs b/crates/air_r_parser_tests/tests/quick_test.rs new file mode 100644 index 00000000..240b66e0 --- /dev/null +++ b/crates/air_r_parser_tests/tests/quick_test.rs @@ -0,0 +1,20 @@ +//! Scratchpad. Run it with `just test-quick-parser`. + +use air_r_parser::RParserOptions; +use air_r_parser::parse; + +#[ignore] +#[test] +fn quick_test() { + let source = r#" +1$foo +1$2 +"#; + + let parsed = parse(source, RParserOptions::default()); + println!("{:#?}", parsed.tree()); + + for diagnostic in parsed.diagnostics() { + println!("{diagnostic:?}"); + } +} diff --git a/crates/air_r_syntax/src/lib.rs b/crates/air_r_syntax/src/lib.rs index 23e78a88..3dc12ba2 100644 --- a/crates/air_r_syntax/src/lib.rs +++ b/crates/air_r_syntax/src/lib.rs @@ -1,6 +1,7 @@ #[macro_use] mod generated; pub mod argument_ext; +pub mod string_ext; mod syntax_node; pub use self::generated::*; @@ -8,6 +9,7 @@ pub use biome_rowan::TextLen; pub use biome_rowan::TextRange; pub use biome_rowan::TextSize; pub use biome_rowan::TokenAtOffset; +pub use biome_rowan::TokenText; pub use biome_rowan::TriviaPieceKind; pub use biome_rowan::WalkEvent; pub use syntax_node::*; diff --git a/crates/air_r_syntax/src/string_ext.rs b/crates/air_r_syntax/src/string_ext.rs new file mode 100644 index 00000000..a9f827a5 --- /dev/null +++ b/crates/air_r_syntax/src/string_ext.rs @@ -0,0 +1,153 @@ +use crate::RStringValue; +use crate::RSyntaxKind::R_DOUBLE_QUOTED_STRING_LITERAL; +use crate::RSyntaxKind::R_RAW_STRING_LITERAL; +use crate::RSyntaxKind::R_SINGLE_QUOTED_STRING_LITERAL; +use crate::RSyntaxToken; +use biome_rowan::SyntaxResult; +use biome_rowan::TextRange; +use biome_rowan::TextSize; +use biome_rowan::TokenText; + +impl RStringValue { + /// The text between the delimiters + /// + /// `"foo"` and `r"---(foo)---"` both give `foo` + pub fn inner_string_text(&self) -> SyntaxResult { + let token = self.value()?; + Ok(token.token_text_trimmed().slice(inner_string_range(&token))) + } +} + +/// Range of the inner text +/// +/// # Safety +/// +/// String literals always carry both of their delimiters. Broken strings are +/// returned from the lexer as `ERROR_TOKEN`s, making this safe on any string we +/// see. +fn inner_string_range(token: &RSyntaxToken) -> TextRange { + let text = token.text_trimmed(); + + let (open, close) = match token.kind() { + R_SINGLE_QUOTED_STRING_LITERAL | R_DOUBLE_QUOTED_STRING_LITERAL => (1, 1), + // `r`, the quote, the hyphens, and the bracket + R_RAW_STRING_LITERAL => { + let hyphens = text.as_bytes()[2..] + .iter() + .take_while(|byte| **byte == b'-') + .count(); + (3 + hyphens, 2 + hyphens) + } + kind => unreachable!("`{kind:?}` is not a string"), + }; + + TextRange::new( + TextSize::from(open as u32), + TextSize::from((text.len() - close) as u32), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::RSyntaxKind; + use crate::RSyntaxKind::R_STRING_VALUE; + use crate::RSyntaxNode; + use biome_rowan::AstNode; + use biome_rowan::TriviaPiece; + + /// The parser cannot be reached from here, so the token is built by hand. + /// `lexer/tests/strings.rs` is what pins that the lexer really produces it. + fn string_value(kind: RSyntaxKind, text: &str, leading: usize) -> RStringValue { + let leading = (leading > 0).then(|| TriviaPiece::whitespace(leading as u32)); + let token = RSyntaxToken::new_detached(kind, text, leading, []); + let node = RSyntaxNode::new_detached(R_STRING_VALUE, [Some(token.into())]); + + RStringValue::cast(node).expect("a string value") + } + + #[track_caller] + fn assert_inner(kind: RSyntaxKind, text: &str, expected: &str, range: TextRange) { + let value = string_value(kind, text, 0); + + assert_eq!(value.inner_string_text().unwrap().text(), expected); + assert_eq!(inner_string_range(&value.value().unwrap()), range); + assert_eq!(&text[range], expected); + } + + fn range(start: u32, end: u32) -> TextRange { + TextRange::new(TextSize::from(start), TextSize::from(end)) + } + + #[test] + fn quoted_strings() { + assert_inner( + R_DOUBLE_QUOTED_STRING_LITERAL, + "\"foo\"", + "foo", + range(1, 4), + ); + assert_inner(R_SINGLE_QUOTED_STRING_LITERAL, "'foo'", "foo", range(1, 4)); + } + + #[test] + fn empty_strings() { + assert_inner(R_SINGLE_QUOTED_STRING_LITERAL, "''", "", range(1, 1)); + assert_inner(R_DOUBLE_QUOTED_STRING_LITERAL, "\"\"", "", range(1, 1)); + assert_inner(R_RAW_STRING_LITERAL, "r\"()\"", "", range(3, 3)); + } + + #[test] + fn raw_strings() { + assert_inner(R_RAW_STRING_LITERAL, "r\"(a)\"", "a", range(3, 4)); + assert_inner(R_RAW_STRING_LITERAL, "r\"---(a)---\"", "a", range(6, 7)); + assert_inner(R_RAW_STRING_LITERAL, "R'--[a]--'", "a", range(5, 6)); + assert_inner(R_RAW_STRING_LITERAL, "r\"-|a|-\"", "a", range(4, 5)); + } + + /// A closing bracket that does not close belongs to the content + #[test] + fn a_raw_string_containing_its_own_bracket() { + assert_inner(R_RAW_STRING_LITERAL, "r\"(())\"", "()", range(3, 5)); + assert_inner(R_RAW_STRING_LITERAL, "r\"|a|b|\"", "a|b", range(3, 6)); + } + + #[test] + fn multiline_strings() { + assert_inner( + R_DOUBLE_QUOTED_STRING_LITERAL, + "\"a\nb\"", + "a\nb", + range(1, 4), + ); + assert_inner(R_RAW_STRING_LITERAL, "r\"(a\nb)\"", "a\nb", range(3, 6)); + } + + /// Escapes are handed back exactly as written + #[test] + fn nothing_is_unescaped() { + assert_inner( + R_DOUBLE_QUOTED_STRING_LITERAL, + "\"a\\nb\"", + "a\\nb", + range(1, 5), + ); + assert_inner( + R_DOUBLE_QUOTED_STRING_LITERAL, + "\"a\\\"b\"", + "a\\\"b", + range(1, 5), + ); + } + + /// The range is off the trimmed token, so leading trivia does not shift it + #[test] + fn trivia_is_not_content() { + let value = string_value(R_DOUBLE_QUOTED_STRING_LITERAL, " \"foo\"", 2); + let token = value.value().unwrap(); + + assert_eq!(token.text_trimmed(), "\"foo\""); + assert_eq!(value.inner_string_text().unwrap().text(), "foo"); + assert_eq!(inner_string_range(&token), range(1, 4)); + } +} diff --git a/crates/air_r_syntax/src/syntax_node.rs b/crates/air_r_syntax/src/syntax_node.rs index ddcce232..4ae311c5 100644 --- a/crates/air_r_syntax/src/syntax_node.rs +++ b/crates/air_r_syntax/src/syntax_node.rs @@ -5,7 +5,8 @@ //! //! This is a simple wrapper around the `rowan` crate which does most of the heavy lifting and is language agnostic. -use crate::{RRoot, RSyntaxKind}; +use crate::RRoot; +use crate::RSyntaxKind; use biome_rowan::Language; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] diff --git a/crates/air_r_test_macros/Cargo.toml b/crates/air_r_test_macros/Cargo.toml new file mode 100644 index 00000000..1eedb51e --- /dev/null +++ b/crates/air_r_test_macros/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "air_r_test_macros" +version = "0.0.0" +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +description = "Test generation macros for the R corpus" +homepage.workspace = true +repository.workspace = true +license.workspace = true +keywords.workspace = true +categories.workspace = true +publish = false + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = { workspace = true } +quote = { workspace = true } + +[lints] +workspace = true diff --git a/crates/air_r_test_macros/src/lib.rs b/crates/air_r_test_macros/src/lib.rs new file mode 100644 index 00000000..30201470 --- /dev/null +++ b/crates/air_r_test_macros/src/lib.rs @@ -0,0 +1,153 @@ +use proc_macro::TokenStream; +use proc_macro2::Ident; +use proc_macro2::Span; +use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::TokenTree; +use quote::quote; +use std::fs; +use std::path::Path; +use std::path::PathBuf; + +/// Expands to one `#[test] fn` per `*.md` under `directory` +/// +/// ```ignore +/// gen_corpus_tests!("tests/corpus", crate::run_file); +/// ``` +#[proc_macro] +pub fn gen_corpus_tests(input: TokenStream) -> TokenStream { + let (directory, runner) = parse_arguments(input.into()); + + // Cargo sets this to the manifest directory of the crate being compiled, + // which is the caller rather than this crate + let manifest = std::env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"); + let root = PathBuf::from(manifest).join(directory); + + let mut module = Module::default(); + collect(&root, &mut module); + module.generate(&runner).into() +} + +fn parse_arguments(input: TokenStream2) -> (String, TokenStream2) { + let mut tokens = input.into_iter(); + + let directory = match tokens.next() { + Some(TokenTree::Literal(literal)) => literal.to_string().trim_matches('"').to_string(), + _ => panic!("expected a string literal naming the corpus directory"), + }; + + match tokens.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == ',' => {} + _ => panic!("expected `,` after the corpus directory"), + } + + (directory, tokens.collect()) +} + +#[derive(Default)] +struct Module { + children: Vec<(String, Module)>, + tests: Vec<(String, String)>, +} + +impl Module { + fn generate(self, runner: &TokenStream2) -> TokenStream2 { + let modules = self.children.into_iter().map(|(name, child)| { + let name = Ident::new(&name, Span::call_site()); + let body = child.generate(runner); + quote! { mod #name { #body } } + }); + + let tests = self.tests.into_iter().map(|(name, path)| { + let name = Ident::new(&name, Span::call_site()); + quote! { + #[test] + fn #name() { + #runner(std::path::Path::new(#path)); + } + } + }); + + quote! { #(#modules)* #(#tests)* } + } +} + +fn collect(directory: &Path, module: &mut Module) { + let mut entries: Vec = fs::read_dir(directory) + .unwrap_or_else(|err| panic!("cannot read {}: {err}", directory.display())) + .map(|entry| entry.expect("cannot read directory entry").path()) + .collect(); + + // read_dir() order is not stable across filesystems + entries.sort(); + + for path in entries { + let name = path + .file_name() + .and_then(|name| name.to_str()) + .expect("corpus paths are UTF-8"); + + if path.is_dir() { + let mut child = Module::default(); + let name = identifier(name); + collect(&path, &mut child); + module.children.push((name, child)); + } else if name.ends_with(".md") && !name.ends_with(".snap.md") { + let test = identifier(name.trim_end_matches(".md")); + module.tests.push((test, path.display().to_string())); + } + } +} + +/// A file or directory name as a Rust identifier +fn identifier(name: &str) -> String { + let mut identifier: String = name + .chars() + .map(|c| if c.is_alphanumeric() { c } else { '_' }) + .collect(); + + if identifier.starts_with(|c: char| c.is_ascii_digit()) || is_keyword(&identifier) { + identifier.push('_'); + } + + identifier +} + +fn is_keyword(name: &str) -> bool { + matches!( + name, + "as" | "break" + | "const" + | "continue" + | "crate" + | "dyn" + | "else" + | "enum" + | "extern" + | "false" + | "fn" + | "for" + | "if" + | "impl" + | "in" + | "let" + | "loop" + | "match" + | "mod" + | "move" + | "mut" + | "pub" + | "ref" + | "return" + | "self" + | "static" + | "struct" + | "super" + | "trait" + | "true" + | "type" + | "unsafe" + | "use" + | "where" + | "while" + ) +} diff --git a/crates/biome_ungrammar/Cargo.lock b/crates/biome_ungrammar/Cargo.lock deleted file mode 100644 index 9ee01fa8..00000000 --- a/crates/biome_ungrammar/Cargo.lock +++ /dev/null @@ -1,21 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ungrammar" -version = "1.16.1" - -[[package]] -name = "ungrammar2json" -version = "1.0.0" -dependencies = [ - "ungrammar", - "write-json", -] - -[[package]] -name = "write-json" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f6174b2566cc4a74f95e1367ec343e7fa80c93cc8087f5c4a3d6a1088b2118" diff --git a/crates/crates/src/snapshots/crates__tests__crate_names.snap b/crates/crates/src/snapshots/crates__tests__crate_names.snap index 30b3240c..65f8d208 100644 --- a/crates/crates/src/snapshots/crates__tests__crate_names.snap +++ b/crates/crates/src/snapshots/crates__tests__crate_names.snap @@ -9,7 +9,9 @@ expression: AIR_CRATE_NAMES "air_r_factory", "air_r_formatter", "air_r_parser", + "air_r_parser_tests", "air_r_syntax", + "air_r_test_macros", "biome_ungrammar", "comments", "crates", @@ -21,6 +23,7 @@ expression: AIR_CRATE_NAMES "tests_macros", "workspace", "xtask", + "xtask_accept", "xtask_codegen", "xtask_json_schema", "zed_air", diff --git a/crates/lsp/Cargo.toml b/crates/lsp/Cargo.toml index 427065d1..a2b5db26 100644 --- a/crates/lsp/Cargo.toml +++ b/crates/lsp/Cargo.toml @@ -27,7 +27,7 @@ dissimilar.workspace = true futures.workspace = true itertools.workspace = true line_ending.workspace = true -serde.workspace = true +serde = { workspace = true, features = ["derive"] } serde_json.workspace = true settings.workspace = true struct-field-names-as-array.workspace = true diff --git a/crates/lsp/src/handlers_format.rs b/crates/lsp/src/handlers_format.rs index 465ee56a..9476261f 100644 --- a/crates/lsp/src/handlers_format.rs +++ b/crates/lsp/src/handlers_format.rs @@ -237,12 +237,28 @@ fn find_deepest_enclosing_logical_lines(node: RSyntaxNode, range: TextRange) -> // ```rust // .filter(|node| range.contains_range(node.text_trimmed_range())) // ``` - let logical_lines: Vec = iter + let mut logical_lines: Vec = iter .map(|statement| statement.into_syntax()) .skip_while(|node| !node.text_range_with_trivia().contains(range.start())) - .take_while(|node| node.text_trimmed_range().start() <= range.end()) .collect(); + let mut len = logical_lines + .iter() + .take_while(|node| node.text_trimmed_range().start() <= range.end()) + .count(); + + // Formatting drops `;`, so a statement sharing a line with the last one must + // come along or the two would be joined + if len > 0 { + while logical_lines + .get(len) + .is_some_and(|node| !node.has_leading_newline()) + { + len += 1; + } + } + + logical_lines.truncate(len); logical_lines } @@ -546,6 +562,126 @@ mod tests { insta::assert_snapshot!(output); } + // Formatting drops `;`, so a range ending before another statement on the + // same line widens to include it + #[tokio::test] + async fn test_format_range_semicolons() { + let mut client = new_test_client().await; + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"<>; b +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"<> b +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"<> ; b +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"<>; b; c +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"a; <>; c +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"<>; c +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"<> +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"{ <>; b } +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"if (x) <>; b +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + // A `;` before a line break is dropped without widening + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"<> +b +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + + #[rustfmt::skip] + let (doc, range) = Document::doodle_and_range( +"<>; # c +b +", + ); + let output = client + .format_document_range(&doc, FileName::Random, range) + .await; + insta::assert_snapshot!(output); + } + #[tokio::test] async fn test_format_range_multiple_lines() { let mut client = new_test_client().await; diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-10.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-10.snap new file mode 100644 index 00000000..bdd92c8e --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-10.snap @@ -0,0 +1,6 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a +b diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-11.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-11.snap new file mode 100644 index 00000000..7bbdbc05 --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-11.snap @@ -0,0 +1,6 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a # c +b diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-2.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-2.snap new file mode 100644 index 00000000..bdd92c8e --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-2.snap @@ -0,0 +1,6 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a +b diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-3.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-3.snap new file mode 100644 index 00000000..bdd92c8e --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-3.snap @@ -0,0 +1,6 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a +b diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-4.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-4.snap new file mode 100644 index 00000000..dad228a7 --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-4.snap @@ -0,0 +1,7 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a +b +c diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-5.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-5.snap new file mode 100644 index 00000000..c09784b9 --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-5.snap @@ -0,0 +1,6 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a; b +c diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-6.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-6.snap new file mode 100644 index 00000000..dad228a7 --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-6.snap @@ -0,0 +1,7 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a +b +c diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-7.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-7.snap new file mode 100644 index 00000000..bdd92c8e --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-7.snap @@ -0,0 +1,6 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a +b diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-8.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-8.snap new file mode 100644 index 00000000..5ace5545 --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-8.snap @@ -0,0 +1,6 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +{ a +b } diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-9.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-9.snap new file mode 100644 index 00000000..33ddae60 --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons-9.snap @@ -0,0 +1,8 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +if (x) { + a +} +b diff --git a/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons.snap b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons.snap new file mode 100644 index 00000000..bdd92c8e --- /dev/null +++ b/crates/lsp/src/snapshots/lsp__handlers_format__tests__format_range_semicolons.snap @@ -0,0 +1,6 @@ +--- +source: crates/lsp/src/handlers_format.rs +expression: output +--- +a +b diff --git a/crates/lsp_test/Cargo.toml b/crates/lsp_test/Cargo.toml index e5111cf2..19ce62be 100644 --- a/crates/lsp_test/Cargo.toml +++ b/crates/lsp_test/Cargo.toml @@ -17,7 +17,7 @@ futures.workspace = true futures-util.workspace = true httparse.workspace = true memchr.workspace = true -serde.workspace = true +serde = { workspace = true, features = ["derive"] } serde_json.workspace = true tokio = { workspace = true, features = ["full"] } tokio-util.workspace = true diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 4a639f74..22f56c32 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -14,13 +14,13 @@ publish = false [dependencies] biome_formatter = { workspace = true, optional = true } schemars = { workspace = true, optional = true } -serde = { workspace = true, optional = true } +serde = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] anyhow = { workspace = true } biome_formatter = { workspace = true } insta = { workspace = true } -serde = { workspace = true } +serde = { workspace = true, features = ["derive"] } # Self dependency to activate optional features for tests. # `cargo shear` will try and incorrectly remove this. settings = { features = ["biome", "serde"], path = "./" } diff --git a/justfile b/justfile index ab2662d5..af3d5cf6 100644 --- a/justfile +++ b/justfile @@ -20,14 +20,23 @@ test *ARGS: test-verbose *ARGS: cargo nextest run --no-capture {{ARGS}} -# Run the insta tests in update mode +# Run the formatter's insta tests in update mode test-insta *ARGS: cargo insta test --test-runner nextest {{ARGS}} +# Regenerate the parser's markdown snapshots, then verify they are stable +test-snapshot-accept: + cargo run -p xtask_accept + cargo nextest run --workspace + # Run the quick formatter test -test-quick: +test-quick-formatter: cargo test --package air_r_formatter --test quick_test -- quick_test --exact --show-output --ignored +# Run the quick parser test +test-quick-parser: + cargo nextest run -p air_r_parser_tests --run-ignored all -E 'test(quick_test)' --no-capture + # Install the air binary to `~/.cargo/bin/air`. # Note that a `~/.local/bin/air` installed another way may shadow this. install-binary: diff --git a/plans/aether.md b/plans/aether.md deleted file mode 100644 index 629d8d4f..00000000 --- a/plans/aether.md +++ /dev/null @@ -1,219 +0,0 @@ -# Moving air onto aether - -Status: ready to start the swap. One open item at the bottom. - -## Goal - -Bring all of aether into air under the `air_` prefix. Aether's crates replace -air's crates of the same role, and aether's test crates come along unchanged. -After the swap, air's parser is aether's hand-written parser, and tree-sitter is -gone. - -This is a plain copy, not a dependency. Air does not pin aether by git, and -there is no `aether_r_*` rename across air's code. If the parser ever needs to -live on its own again, it can be split back out. - -Success means: - -- `crates/air_r_parser` is aether's parser, and `tree-sitter` and - `tree-sitter-r` are gone from the workspace. - -- `air_r_parser_tests` and `air_r_test_macros` exist, and `just accept` and - `just test-quick-parser` work. - -- Every formatter spec snapshot is unchanged, except for new specs covering - input that only aether can parse. - -## Rule for collisions - -When air and aether disagree on a file, crate, setting, or dependency, aether -wins. Exceptions are listed under Decisions. - -## Where the two repos stand - -The prep work (P1 to P7, all merged) has brought air's tree-sitter walker in -line with aether's tree shape. What's left to reconcile is small: - -- `r.ungram`: byte-identical. - -- `air_r_syntax` vs `aether_r_syntax`, after swapping the prefix: aether adds - `string_ext.rs` and `pub use biome_rowan::TokenText`. It also splits one - `use crate::{RRoot, RSyntaxKind}` into two lines. - -- `air_r_factory` vs `aether_r_factory`: identical after swapping the prefix. - -- `biome_ungrammar`: identical, except air has a stray `Cargo.lock`. - -- `air_r_parser` vs `aether_r_parser`: same public API (`RParse`, `parse()`, - `RParserOptions`, `has_errors()`, `diagnostics()`, `into_diagnostics()`, - `From for AnyParse`). The internals are fully replaced. - -- `xtask`: aether's is a superset of air's. It adds `accept.rs`, a `main.rs`, - a `bpaf` `TaskCommand`, and a `project_root()` that searches for - `[workspace]`. - -- `xtask/codegen`: aether's is air's minus `formatter.rs`, the `Formatter` and - `All` commands, and `LanguageKind::formatter_crate_name()`. - -- Air's `tests_macros` stays. The formatter spec harness still uses it. - `air_r_test_macros` is a different, smaller macro for the markdown corpus. - -## The stack - -Two prep PRs on `main`, then one swap PR. - -### Prep 1: drop the tree-sitter LSP view - -Self-contained, and it can land now. - -- `crates/lsp/src/handlers_ext.rs`: remove `ViewFileKind::TreeSitter` and - `format_ts_node()`. - -- `crates/lsp/Cargo.toml`: drop `tree-sitter` and `tree-sitter-r`. - -- `editors/code`: remove `air.viewTreeSitter` from `commands.ts`, - `package.json`, and the `"TreeSitter"` member of `ViewFileKind` in - `request/viewFile.ts`. - -- Changelog bullet for the VS Code extension. - -### Prep 2: `test-health.yml` with fmt and clippy - -Air's CI runs neither today. - -- New `.github/workflows/test-health.yml`, wired into `test.yml`. It runs - `cargo fmt --all --check` and - `cargo clippy --workspace --all-targets -- -D warnings` on Linux. - -- Fix whatever they flag in air's code, in its own commit. - -- Take aether's `rust-toolchain.toml`, which adds - `components = ["rustfmt", "clippy"]`. - -- Rename the clippy lint `empty_enum` to `empty_enums`, as aether does. - -The swap PR adds two more checks to the same workflow. - -### The swap PR - -Each bullet is roughly one commit. - -- [ ] Copy aether's crates in, renaming `aether` to `air` in crate names, - paths, idents, and descriptions: - - `aether_r_parser` replaces `crates/air_r_parser` wholesale, including - deleting `treesitter.rs`, the old spec harness, and the 78 fixtures under - `tests/snapshots/`. Aether's corpus replaces them. - - `aether_r_syntax` replaces `crates/air_r_syntax`. - - `aether_r_factory` replaces `crates/air_r_factory`. - - `aether_r_parser_tests` arrives as `crates/air_r_parser_tests`. - - `aether_r_test_macros` arrives as `crates/air_r_test_macros`. - - `biome_ungrammar` replaces air's copy, dropping the stray `Cargo.lock`. - -- [ ] `xtask`: aether's `xtask` replaces air's, renamed. Add `"xtask"` to - workspace `members`, since it now has a binary. - -- [ ] `xtask/codegen`: start from aether's crate, then re-add air's - `formatter.rs` unchanged (`git2` included), the `Formatter` and `All` - commands, and `formatter_crate_name()`. Run `just gen-grammar` and - `just gen-formatter`. Both should produce no diff. - -- [ ] Workspace `Cargo.toml`: - - Add `air_r_parser_tests`, `air_r_test_macros`, `biome_console`, - `biome_diagnostics`, `bpaf`, `similar`, and `unicode-ident`. - - Give `serde` the `derive` feature, as aether does. - - Remove `tree-sitter` and `tree-sitter-r`, plus `biome_unicode_table` if - nothing else uses it. - -- [ ] `.config/nextest.toml`: take aether's, verbatim. - -- [ ] `justfile`: - - `gen-grammar` stays, and runs the merged codegen. - - Aether's `test` is dropped. Air's `test *ARGS` stays. - - Aether's `accept` is added as-is. - - Aether's `quick` is added as `test-quick-parser`. - - Air's `test-quick` becomes `test-quick-formatter`. - -- [ ] `test-health.yml`: add aether's two checks, which are - `just gen-grammar && git diff --exit-code` and - `cargo run -p xtask -- accept && git diff --exit-code`. - -- [ ] Formatter specs for semicolons, replacing the hand-built `TODO(aether)` - tests in `r/auxiliary/empty_statement.rs`. New `statements/semicolons.R` - covers: - - `1;2`, `1;`, `1;;2` in braces, `{;}`, `{;1}`. - - `1; # c` and `1 ; # c`. - - A comment before a `;` on the next line, as in `{1 # c` then `;}`. - - Blank lines around a lone `;` in braces. - - `# fmt: skip` on a statement ending in `;` (kept verbatim, `;` included). - - Curly-curly with a `;`: `fn({{ x }; })` and `fn({{ x; }})` both format - to `fn({{ x }})`. - - Every case from the hand-built tests. - -- [ ] Formatter specs for input only aether can parse: `r"-|a|-"` raw strings, - `%a\b%`, and `for (... in x)`. - -- [ ] Range formatting across `;` in the LSP tests. - -- [ ] Snapshots: the `crates/crates` crate list and the LSP `document_syntax` - snapshots. - -- [ ] `CLAUDE.md`: - - Root: update the crate layout, say that the parser is hand-written, and - document `just accept` and `just test-quick-parser`. - - New nested `crates/air_r_parser/CLAUDE.md`, a trimmed copy of aether's. It - keeps the invariants, the corpus test format, and the reference - implementations. It drops the `deviations.md` references, since that file - doesn't come over. - -- [ ] Docs: `CONTRIBUTING.md`, `xtask/README.md`, `xtask/codegen/README.md`. - -- [ ] `CHANGELOG.md`: - - Files R rejects are now rejected: two expressions on one line, a stray - top-level `;`, `pkg::` followed by a newline, `x$1`. - - `r"-|...|-"`, `%a\b%`, and `for (... in x)` now parse. - - Building air no longer needs a C compiler. - - Performance, if the benchmark shows a win. - -- [ ] Validation before merge: - - Ecosystem diff: format a fixed list of GitHub repos, cloned at pinned SHAs, - with `main` and with the branch, then diff. Every formatting difference must - be explained, and every file that newly fails to parse must be one R also - rejects. - - CLI benchmark on the same corpus. - -## Decisions - -- Plain copy, not a git subtree. Blame for parser files starts at the import - commit. - -- One swap PR after two prep PRs, not a longer stack. - -- Aether's `docs/deviations.md` does not come over. - -- Aether's `CLAUDE.md` becomes a nested `crates/air_r_parser/CLAUDE.md`. - -- `formatter.rs` comes over unchanged, `git2` and all. - -- Aether's `.config/nextest.toml` is taken verbatim, including the 3×10s - terminate. Any LSP test that trips it gets fixed. - -- The CLI keeps today's one-line parse error. Rich diagnostics are a follow-up. - -- What happens to the aether repo is out of scope. - -## Follow-ups after the swap - -- Print parse diagnostics with locations from the CLI, instead of "Failed to - parse due to syntax errors." This is the `TODO(aether)` in - `crates/workspace/src/format.rs`. - -- Consider publishing parse diagnostics from the LSP, now that they carry - ranges. - -- Consider formatting around local errors, since the parser keeps the rest of - the tree intact. Air refuses today, and that stays true in this plan. - -## Open - -1. The repo list for the ecosystem diff. It needs to go here before the swap PR - is opened. diff --git a/plans/diagnostics.md b/plans/diagnostics.md new file mode 100644 index 00000000..01dffaec --- /dev/null +++ b/plans/diagnostics.md @@ -0,0 +1,309 @@ +# Parse diagnostics in the CLI + +Status: draft, ready for review. + +## Goal + +When `air format` can't parse a file, show the user where and why, with a code +frame, instead of a single opaque line. + +Today: + +``` +ERROR Failed to format R/foo.R: Failed to parse due to syntax errors. +``` + +After: + +``` +R/foo.R:3:8 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an expression but instead found ')'. + + 1 │ f <- function(x) { + 2 │ y <- 1 + > 3 │ g(x, ) + │ ^ + 4 │ } + 5 │ + +ERROR Failed to parse 1 file. +``` + +This resolves the `TODO(aether)` in `crates/workspace/src/format.rs`. + +## Where things stand + +- `air_r_parser::parse()` returns an `RParse` whose `diagnostics()` are + `biome_parser::diagnostic::ParseDiagnostic`s. Each carries a `TextRange` + span, a message, and optional detail advices with their own spans, as in + "Reached the end of the file looking for a closing delimiter". + +- `workspace::format::format_source()` already hands them back, in + `FormatSourceError::Parse(Vec)`. The CLI only ever prints + that variant's `Display`, so the diagnostics are dropped. + +- `format_path()` in `crates/air/src/commands/format/paths.rs` and + `format_stdin()` in `stdin.rs` both drop the source text on the error + path. The renderer needs that text to turn byte offsets into lines and + columns. + +- `air_r_parser_tests` already renders these diagnostics into the corpus + snapshots, using `biome_diagnostics::PrintDiagnostic::verbose()` with + `with_file_path()` and `with_file_source_code()`. The CLI takes the same + path. Both move to `simple()`, which prints the same bytes as `verbose()` + for parse diagnostics today, so users see what the corpus snapshots show. + +- How air decides on color today is inconsistent: + + - `colored` checks whether stdout is a terminal, but everything it colors + goes to stderr. + + - `tracing-subscriber` only checks `NO_COLOR`, and never checks for a + terminal. + + So `air format . 2>log` run in a terminal writes ANSI codes into `log`. + +- The LSP never calls `format_source()`. It checks `has_errors()` itself and + is unaffected. + +## Renderer + +Use `biome_diagnostics`, which is already a workspace dependency. Things +worth knowing about it: + +- Columns are counted in characters, not bytes, so non-ASCII R code reports + the right column. Lines split on `\n`, `\r\n`, and a lone `\r`, and the + frame trims line endings, so CRLF files render cleanly. This matters + because the CLI doesn't normalize line endings. + +- Frames show two lines of context, and frames longer than 8 lines get an + ellipsis. + +- Tabs are printed as tabs, and carets are padded by display width, so + markers line up under tabs and wide characters. + +- The `━` rule after the header is padded to 100 columns in debug builds and + to the terminal width in release builds. It measures the terminal on + stdout, with a fallback of 100. Integration tests run a debug binary, so + snapshots are stable. + +- The header changes with the terminal: under `TERM_PROGRAM=vscode` it skips + the hyperlink, under JetBrains it prefixes ` at `, and otherwise an + absolute path becomes an OSC 8 `file://` link. We pass relative paths + whenever the file is under the working directory, so in practice only + files outside it get links. This is acceptable as is. + +- On Windows, `biome_console` swaps a few emoji for ASCII. `×` is already + ASCII-safe, and the corpus snapshots pass on Windows CI today. + +## Commits + +1. Decide color once, based on stderr. This fixes the existing inconsistency + on its own, before any diagnostics work. + +2. Render parse diagnostics. + +## Design + +### Output streams + +stdout carries only formatted code, in stdin mode. Everything else goes to +stderr, as it does today. That includes the code frames, the `ERROR` lines, +the summary, and `Would reformat:`. + +For comparison: + +- biome sends diagnostics to stderr, and the run summary to stdout. + +- `ruff check` and `ty check` send diagnostics to stdout, as the command's + result. + +- `ruff format` puts parse errors on stdout as code frames under `--check`. + In write mode and stdin mode, it falls back to one-line `error!` messages on + stderr, because stdout is taken. + +Air can't use stdout for diagnostics, because in stdin mode stdout is the +formatted file. Using stderr everywhere gives full code frames in every mode, +including the editor's stdin path. Moving `Would reformat:` to stdout to match +ruff would be a breaking change, and is out of scope. + +### Color + +Commit 1, in `crates/air/src/logging.rs`, or wherever `run()` sets up +logging: + +- Compute one `bool` at startup: + `!no_color && NO_COLOR is unset or empty && stderr().is_terminal()`. + +- Apply it everywhere, with `colored::control::set_override(color)` and + `layer.with_ansi(color)`. This replaces `turn_off_colors()`. + `set_override(true)` is only reached once `NO_COLOR` has already been + checked, so it never overrides it. + +- Every later caller reads `colored::control::SHOULD_COLORIZE.should_colorize()`, + as ruff does, so there is one source of truth and nothing to pass around. + +- `CLICOLOR` and `CLICOLOR_FORCE` stop having an effect. `colored` only + respected them by accident, and they are not documented in `--help`. + `FORCE_COLOR` is not added. + +Commit 2 maps `should_colorize()` to `ColorChoice::Always` or +`ColorChoice::Never` for the `termcolor::StandardStream::stderr()` the +diagnostics are written to. + +### `workspace` + +- Delete the `TODO(aether)` comment. `FormatSourceError::Parse` keeps its + payload and its `Display`, which stays the fallback for any caller that + doesn't render diagnostics. + +### `air` crate + +- Add `biome_console` and `biome_diagnostics` as dependencies. + `biome_diagnostics` re-exports `termcolor`, so nothing else is needed. + +- New module `crates/air/src/commands/format/diagnostics.rs`, shared by + `paths.rs` and `stdin.rs`, with two functions: + + - A `ParseFailure` struct, holding a display path, the source text, and + the `Vec`. + + - `print_parse_failures(failures: &[ParseFailure], writer: &mut impl WriteColor)`, + which renders every diagnostic of every failure, in order. + +- Switch `render_diagnostics()` in `crates/air_r_parser_tests/src/check.rs` + from `verbose()` to `simple()`, so the corpus snapshots keep showing exactly + what the CLI prints. `ParseDiagnostic` has no verbose advices, so no + snapshot changes, and `just accept` should produce no diff. + +- Each diagnostic is rendered as biome's terminal reporter does: + `diagnostic.with_file_path(path).with_file_source_code(source)`, then + `PrintDiagnostic::simple()` through + `biome_console::fmt::Formatter::new(&mut Termcolor(writer))`. Attaching the + source copies it once per diagnostic. That's fine, since this only runs + when parsing fails. + +- Print every diagnostic. There is no cap and no `--max-diagnostics` flag. + +### `paths.rs` + +- `FormatPathError` gains a `Parse(ParseFailure)` variant. `format_path()` + matches `FormatSourceError::Parse(diagnostics)` and moves `old` and the + relativized path into it. All other `FormatSourceError` variants keep + going to `FormatPathError::Format`. + +- In both `FormatMode::Write` and `FormatMode::Check`, `format()` splits the + collected errors into parse failures and everything else, then: + + 1. Sorts the parse failures by path. Discovery runs in parallel, so without + sorting the output order wouldn't be deterministic. + + 2. Prints them with `print_parse_failures()` to a locked + `termcolor::StandardStream::stderr()`. + + 3. Logs the other errors with `tracing::error!`, as today. + + 4. If any files failed to parse, logs one summary through + `tracing::error!`: `Failed to parse 1 file.` or + `Failed to parse 3 files.`. + + 5. In check mode, prints the `Would reformat:` lines, as today. + +- The exit status doesn't change. Any error still gives `ExitStatus::Error`. + +### `stdin.rs` + +- `FormatStdinError` gains `Parse(ParseFailure)`. `format_stdin()` takes + the `--stdin-file-path` path, which is required for stdin, and uses + `relativize_path()` on it for the header, so editors that pipe through + stdin show a real file location. + +- When parsing fails, it prints the failure and then + `Failed to parse 1 file.`, matching the paths output. + +### Log level + +- The diagnostic frames are written straight to stderr, not through + `tracing`, so `--log-level` doesn't filter them. They are the command's + output, like `Would reformat:`. The summary line does go through + `tracing`, so it does respect the log level. + +## Tests + +All as integration snapshots in `crates/air/tests/integration/format.rs`, +run with `--no-color`: + +- One file, one error, in write mode. Replaces the snapshot for + `stdin_errors_on_parse_error`, which moves to the new output. + +- One file with several errors, to check that every one prints in source + order. + +- An error with a detail advice, like an unterminated string, to check that + the second frame renders. + +- Two broken files and one good file, passed in reverse alphabetical order, + to check the sorted output and `Failed to parse 2 files.`. + +- `--check` with one broken file and one that needs formatting, to check that + the diagnostics come before `Would reformat:` and the exit code is an + error. + +- A CRLF file, to check that the frame has no stray `\r`. + +- An error after a non-ASCII character on the same line, to check that the + column counts characters. + +- Stdin with `--stdin-file-path R/foo.R`, to check that the header shows + `R/foo.R`. + +- Use `.normalize_os_path_separator()` where a header has a nested path. + +- Commit 1: a test with stderr piped and no `--no-color`, checking that the + `ERROR` label and `Would reformat:` contain no ANSI codes. Integration + tests always pipe stderr, so every existing snapshot already exercises the + case without a terminal. + +## CHANGELOG + +- Under the CLI section: "Files that fail to parse now report each syntax + error with its location and a code frame, instead of 'Failed to parse due + to syntax errors.'" + +- Under the CLI section: "Colored output is now turned off when stderr is not + a terminal, so redirecting stderr to a file no longer writes ANSI codes." + +## Decisions + +- Render with `biome_diagnostics`, not `annotate-snippets`, so the CLI matches + the corpus snapshots. + +- Print every diagnostic, with no cap. + +- Print the frames, then an `ERROR Failed to parse N file(s).` summary. The + old per-file `ERROR Failed to format …` line is dropped for parse errors. + +- Under stdin, the header shows the path from `--stdin-file-path`. + +- Everything goes to stderr except formatted code. That includes the code + frames, even under `--check`. + +- Color is decided once, from stderr, `--no-color`, and `NO_COLOR`, then + pushed into `colored` and `tracing-subscriber`. + +- Use `PrintDiagnostic::simple()`, as biome's CLI does, in both the CLI and + the corpus harness. + +- Copy the source per diagnostic with `with_file_source_code()`, rather than + writing a borrowing `Diagnostic` wrapper. It's simpler, and this only + runs on failure. + +## Out of scope + +- Publishing parse diagnostics from the LSP. + +- Formatting around local parse errors. + +- A `--max-diagnostics` flag. If cascades from error recovery turn out to be + noisy in practice, this is where we'd revisit it. diff --git a/xtask/README.md b/xtask/README.md index 5f286591..31238766 100644 --- a/xtask/README.md +++ b/xtask/README.md @@ -1,5 +1,5 @@ # xtask -This directory contains Rust scripts for code generation. +This directory contains Rust scripts for development tasks. Check the `justfile` for usage. diff --git a/xtask/accept/Cargo.toml b/xtask/accept/Cargo.toml new file mode 100644 index 00000000..6cd4c4c9 --- /dev/null +++ b/xtask/accept/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "xtask_accept" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +air_r_parser_tests = { workspace = true } +xtask = { version = "0.0", path = "../" } + +[lints] +workspace = true diff --git a/xtask/accept/src/main.rs b/xtask/accept/src/main.rs new file mode 100644 index 00000000..e4aa617f --- /dev/null +++ b/xtask/accept/src/main.rs @@ -0,0 +1,62 @@ +use std::fs; + +use air_r_parser_tests::check; +use air_r_parser_tests::corpus; +use air_r_parser_tests::Document; +use xtask::anyhow; +use xtask::project_root; +use xtask::pushd; +use xtask::Result; + +fn main() -> Result<()> { + let _d = pushd(project_root()); + accept() +} + +/// Regenerates every corpus snapshot +fn accept() -> Result<()> { + for input_path in corpus::input_files() { + let display = corpus::relative(&input_path); + let mut document = Document::parse(&fs::read_to_string(&input_path)?) + .map_err(|err| anyhow!("{display}: {err}"))?; + + for section in &mut document.sections { + let expectation = section.expectation; + for block in section.blocks_mut() { + // On failure write what we can and let the test run report it: + // a regeneration should be maximally informative + block.generated = check(block, expectation).unwrap_or_default(); + } + } + + let snapshot_path = corpus::snapshot_of(&input_path); + let rendered = document.render_snapshot(); + + if fs::read_to_string(&snapshot_path).ok().as_deref() != Some(&rendered) { + fs::write(&snapshot_path, rendered)?; + println!("updated {}", corpus::relative(&snapshot_path)); + } + } + + remove_orphan_snapshots() +} + +/// A deleted input file leaves its snapshot behind +fn remove_orphan_snapshots() -> Result<()> { + for snapshot in corpus::snapshot_files() { + let name = snapshot + .file_name() + .and_then(|name| name.to_str()) + .expect("corpus paths are UTF-8"); + + if !snapshot + .with_file_name(name.replace(".snap.md", ".md")) + .exists() + { + fs::remove_file(&snapshot)?; + println!("removed orphan {}", corpus::relative(&snapshot)); + } + } + + Ok(()) +} diff --git a/xtask/codegen/Cargo.toml b/xtask/codegen/Cargo.toml index dfc12e62..14ef4dfb 100644 --- a/xtask/codegen/Cargo.toml +++ b/xtask/codegen/Cargo.toml @@ -8,8 +8,8 @@ publish = false anyhow = { workspace = true } biome_string_case = { workspace = true } biome_ungrammar = { workspace = true } -bpaf = { version = "0.9.15", features = ["derive"] } -git2 = { version = "0.21.0", default-features = false } +bpaf = { workspace = true } +git2 = { workspace = true } proc-macro2 = { workspace = true, features = ["span-locations"] } quote = { workspace = true } xtask = { version = "0.0", path = "../" } diff --git a/xtask/codegen/README.md b/xtask/codegen/README.md index ebbe7cb4..6662ea49 100644 --- a/xtask/codegen/README.md +++ b/xtask/codegen/README.md @@ -2,7 +2,7 @@ This crate contains local commands used to auto-generate source code. -## `cargo codegen grammar` +## `just gen-grammar` This command transforms the `*.ungram` files into the `air_*_syntax` and `air_*_factory` crates. @@ -11,3 +11,9 @@ The project uses a fork of [`ungrammar`](https://github.com/rust-analyzer/ungram `ungrammar` uses a DSL to define and parse the grammar of a language. Once the library parses the DSL files, some custom logic generates the AST APIs. + +## `just gen-formatter` + +This command creates a stub formatting rule in `air_r_formatter` for each node that doesn't have one yet, and regenerates `air_r_formatter/src/generated.rs`. Existing rules are never overwritten. + +It stages the files it writes, and stops with an error if a file it would write has uncommitted changes. diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 39790ced..c28158d4 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -19,13 +19,18 @@ pub enum Mode { } pub fn project_root() -> PathBuf { - Path::new( - &env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()), - ) - .ancestors() - .nth(2) - .unwrap() - .to_path_buf() + let manifest_dir = + env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| env!("CARGO_MANIFEST_DIR").to_owned()); + + // Searched rather than counted, so it holds wherever the calling crate sits + Path::new(&manifest_dir) + .ancestors() + .find(|directory| { + std::fs::read_to_string(directory.join("Cargo.toml")) + .is_ok_and(|manifest| manifest.contains("[workspace]")) + }) + .expect("a crate in this workspace runs this") + .to_path_buf() } pub fn reformat(text: impl Display) -> Result {