From cd9e2db52b3968a846f1337f9202183c21b58f4a Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Mon, 13 Apr 2026 18:26:12 -0300 Subject: [PATCH] fix: Support compiling without default features Update internal dependencies in `plotnik-compiler`, `plotnik-lib`, and `plotnik-cli` to explicitly set `default-features = false`. This prevents Cargo from implicitly enabling all languages when these crates are depended upon. The `plotnik-langs` feature is now explicitly delegated through the dependency graph. - Fully qualify paths in `define_langs!` to prevent unused import warnings when no language features are enabled - Add a CI job to test various feature flag combinations - Update the version bump script to handle explicit dependency versions --- .github/workflows/bump.yml | 5 +- .github/workflows/stable.yml | 30 ++ Cargo.lock | 56 --- crates/plotnik-cli/Cargo.toml | 2 +- crates/plotnik-compiler/Cargo.toml | 3 +- .../src/compile/collapse_up_tests.rs | 2 +- .../src/compile/compile_tests.rs | 12 +- .../plotnik-compiler/src/compile/compiler.rs | 2 +- .../src/compile/lower_tests.rs | 66 ++-- .../plotnik-compiler/src/emit/emit_tests.rs | 354 ++++++++++++------ crates/plotnik-compiler/src/emit/layout.rs | 15 +- .../plotnik-compiler/src/emit/layout_tests.rs | 36 +- .../tests/grammar/alternations_tests.rs | 102 +++-- .../src/parser/tests/grammar/anchors_tests.rs | 36 +- .../parser/tests/grammar/captures_tests.rs | 96 +++-- .../parser/tests/grammar/definitions_tests.rs | 66 ++-- .../src/parser/tests/grammar/fields_tests.rs | 42 ++- .../src/parser/tests/grammar/nodes_tests.rs | 84 +++-- .../parser/tests/grammar/quantifiers_tests.rs | 30 +- .../parser/tests/grammar/sequences_tests.rs | 56 ++- .../src/parser/tests/grammar/special_tests.rs | 66 ++-- crates/plotnik-langs/src/builtin.rs | 10 +- crates/plotnik-lib/Cargo.toml | 4 +- docs/lang-reference.md | 63 ++-- 24 files changed, 776 insertions(+), 462 deletions(-) diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 37b8136..9ecb615 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -60,8 +60,11 @@ jobs: tomato set workspace.dependencies.plotnik-vm.version "$next" Cargo.toml tomato set workspace.dependencies.plotnik-langs.version "$next" Cargo.toml tomato set workspace.dependencies.plotnik-lib.version "$next" Cargo.toml - # plotnik-cli has explicit plotnik-langs dep (default-features = false workaround) + # explicit default-features = false overrides need local version bumps too + tomato set dependencies.plotnik-langs.version "$next" crates/plotnik-compiler/Cargo.toml + tomato set dependencies.plotnik-compiler.version "$next" crates/plotnik-lib/Cargo.toml tomato set dependencies.plotnik-langs.version "$next" crates/plotnik-cli/Cargo.toml + tomato set dependencies.plotnik-lib.version "$next" crates/plotnik-cli/Cargo.toml - name: Update lockfile run: cargo check --workspace diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index bd02b2b..a1f03dc 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -50,3 +50,33 @@ jobs: - name: Test run: make test + + feature-flags: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + + - name: Using Rust cache + uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 + with: + cache-all-crates: "true" + cache-on-failure: "true" + prefix-key: v0-rust-stable + + - name: No-default-features langs + run: cargo clippy -p plotnik-langs --no-default-features -- -D warnings + + - name: No-default-features compiler + run: cargo check -p plotnik-compiler --no-default-features + + - name: Single non-default language + run: cargo check -p plotnik --no-default-features --features lang-styx + + - name: All-features workspace + run: cargo check --workspace --all-targets --all-features diff --git a/Cargo.lock b/Cargo.lock index 4d7859e..e7dc5a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -962,61 +962,6 @@ dependencies = [ "tree-sitter-language", ] -[[package]] -name = "arborium-styx" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9482afa498f41904ea0753acd9dba43863996f172534d76189daa62388dac62" -dependencies = [ - "arborium-sysroot", - "cc", - "tree-sitter-language", -] - -[[package]] -name = "arborium-styx" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9482afa498f41904ea0753acd9dba43863996f172534d76189daa62388dac62" -dependencies = [ - "arborium-sysroot", - "cc", - "tree-sitter-language", -] - -[[package]] -name = "arborium-styx" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9482afa498f41904ea0753acd9dba43863996f172534d76189daa62388dac62" -dependencies = [ - "arborium-sysroot", - "cc", - "tree-sitter-language", -] - -[[package]] -name = "arborium-styx" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9482afa498f41904ea0753acd9dba43863996f172534d76189daa62388dac62" -dependencies = [ - "arborium-sysroot", - "cc", - "tree-sitter-language", -] - -[[package]] -name = "arborium-styx" -version = "2.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9482afa498f41904ea0753acd9dba43863996f172534d76189daa62388dac62" -dependencies = [ - "arborium-sysroot", - "cc", - "tree-sitter-language", -] - [[package]] name = "arborium-svelte" version = "2.16.0" @@ -1885,7 +1830,6 @@ dependencies = [ "plotnik-bytecode", "plotnik-compiler", "plotnik-core", - "plotnik-langs", "plotnik-vm", "serde", "serde_json", diff --git a/crates/plotnik-cli/Cargo.toml b/crates/plotnik-cli/Cargo.toml index 06f718d..6b3f724 100644 --- a/crates/plotnik-cli/Cargo.toml +++ b/crates/plotnik-cli/Cargo.toml @@ -248,7 +248,7 @@ lang-zsh = ["plotnik-langs/lang-zsh"] clap = { version = "4.5", features = ["derive"] } plotnik-core.workspace = true plotnik-langs = { path = "../plotnik-langs", version = "0.3.3", default-features = false } -plotnik-lib.workspace = true +plotnik-lib = { path = "../plotnik-lib", version = "0.3.3", default-features = false } arborium-tree-sitter = "2.12.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/crates/plotnik-compiler/Cargo.toml b/crates/plotnik-compiler/Cargo.toml index cd2d9d0..9d3319f 100644 --- a/crates/plotnik-compiler/Cargo.toml +++ b/crates/plotnik-compiler/Cargo.toml @@ -12,7 +12,7 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } [dependencies] plotnik-bytecode.workspace = true plotnik-core.workspace = true -plotnik-langs = { workspace = true, optional = true } +plotnik-langs = { path = "../plotnik-langs", version = "0.3.3", default-features = false } annotate-snippets = "0.12.9" logos = "0.16.0" indexmap = "2" @@ -27,6 +27,7 @@ regex-syntax = "0.8" [features] default = ["plotnik-langs"] +plotnik-langs = ["plotnik-langs/default"] [dev-dependencies] insta = { version = "=1.46.1", features = ["yaml"] } diff --git a/crates/plotnik-compiler/src/compile/collapse_up_tests.rs b/crates/plotnik-compiler/src/compile/collapse_up_tests.rs index d1b85b7..5d2d746 100644 --- a/crates/plotnik-compiler/src/compile/collapse_up_tests.rs +++ b/crates/plotnik-compiler/src/compile/collapse_up_tests.rs @@ -2,8 +2,8 @@ use plotnik_bytecode::Nav; -use super::collapse_up::collapse_up; use super::CompileResult; +use super::collapse_up::collapse_up; use crate::bytecode::{InstructionIR, Label, MatchIR}; #[test] diff --git a/crates/plotnik-compiler/src/compile/compile_tests.rs b/crates/plotnik-compiler/src/compile/compile_tests.rs index 5618ce9..4092afe 100644 --- a/crates/plotnik-compiler/src/compile/compile_tests.rs +++ b/crates/plotnik-compiler/src/compile/compile_tests.rs @@ -36,7 +36,8 @@ fn compile_nested() { fn compile_large_tagged_alternation() { // Regression test: alternations with 30+ branches should compile // by splitting epsilon transitions into a cascade. - shot_bytecode!(r#" + shot_bytecode!( + r#" Q = [ A0: (identifier) @x0 A1: (identifier) @x1 A2: (identifier) @x2 A3: (identifier) @x3 A4: (identifier) @x4 A5: (identifier) @x5 @@ -49,7 +50,8 @@ fn compile_large_tagged_alternation() { A24: (identifier) @x24 A25: (identifier) @x25 A26: (identifier) @x26 A27: (identifier) @x27 A28: (identifier) @x28 A29: (identifier) @x29 ] - "#); + "# + ); } #[test] @@ -66,8 +68,10 @@ fn compile_unlabeled_alternation_5_branches_with_captures() { fn compile_unlabeled_alternation_8_branches_with_captures() { // Even more extreme: 8 branches means 14 pre-effects per branch (7 nulls + 7 sets). // This requires 2 cascade steps per branch. - shot_bytecode!(r#" + shot_bytecode!( + r#" Q = [(identifier) @a (number) @b (string) @c (binary_expression) @d (call_expression) @e (member_expression) @f (array) @g (object) @h] - "#); + "# + ); } diff --git a/crates/plotnik-compiler/src/compile/compiler.rs b/crates/plotnik-compiler/src/compile/compiler.rs index 866be99..2ea1e8f 100644 --- a/crates/plotnik-compiler/src/compile/compiler.rs +++ b/crates/plotnik-compiler/src/compile/compiler.rs @@ -17,8 +17,8 @@ use super::collapse_prefix::collapse_prefix; use super::collapse_up::collapse_up; use super::dce::remove_unreachable; use super::epsilon_elim::eliminate_epsilons; -use super::lower::lower; use super::error::{CompileError, CompileResult}; +use super::lower::lower; use super::scope::StructScope; use super::verify::debug_verify_ir_fingerprint; diff --git a/crates/plotnik-compiler/src/compile/lower_tests.rs b/crates/plotnik-compiler/src/compile/lower_tests.rs index a2eb730..e1fcc42 100644 --- a/crates/plotnik-compiler/src/compile/lower_tests.rs +++ b/crates/plotnik-compiler/src/compile/lower_tests.rs @@ -1,9 +1,9 @@ //! Unit tests for the lowering pass. -use plotnik_bytecode::{EffectOpcode, Nav, MAX_MATCH_PAYLOAD_SLOTS, MAX_PRE_EFFECTS}; +use plotnik_bytecode::{EffectOpcode, MAX_MATCH_PAYLOAD_SLOTS, MAX_PRE_EFFECTS, Nav}; -use super::lower::lower; use super::CompileResult; +use super::lower::lower; use crate::bytecode::{EffectIR, InstructionIR, Label, MatchIR}; const MAX_POST_EFFECTS: usize = 7; @@ -16,10 +16,12 @@ fn make_effect(_idx: u16) -> EffectIR { #[test] fn lower_no_overflow_unchanged() { let mut result = CompileResult { - instructions: vec![MatchIR::at(Label(0)) - .pre_effects((0..3).map(make_effect)) - .next(Label(1)) - .into()], + instructions: vec![ + MatchIR::at(Label(0)) + .pre_effects((0..3).map(make_effect)) + .next(Label(1)) + .into(), + ], def_entries: Default::default(), preamble_entry: Label(0), }; @@ -32,11 +34,13 @@ fn lower_no_overflow_unchanged() { #[test] fn lower_pre_effects_overflow() { let mut result = CompileResult { - instructions: vec![MatchIR::at(Label(0)) - .nav(Nav::Down) - .pre_effects((0..10).map(make_effect)) - .next(Label(1)) - .into()], + instructions: vec![ + MatchIR::at(Label(0)) + .nav(Nav::Down) + .pre_effects((0..10).map(make_effect)) + .next(Label(1)) + .into(), + ], def_entries: Default::default(), preamble_entry: Label(0), }; @@ -59,11 +63,13 @@ fn lower_pre_effects_overflow() { #[test] fn lower_post_effects_overflow() { let mut result = CompileResult { - instructions: vec![MatchIR::at(Label(0)) - .nav(Nav::Down) - .post_effects((0..10).map(make_effect)) - .next(Label(1)) - .into()], + instructions: vec![ + MatchIR::at(Label(0)) + .nav(Nav::Down) + .post_effects((0..10).map(make_effect)) + .next(Label(1)) + .into(), + ], def_entries: Default::default(), preamble_entry: Label(0), }; @@ -89,11 +95,13 @@ fn lower_post_effects_overflow() { #[test] fn lower_neg_fields_overflow() { let mut result = CompileResult { - instructions: vec![MatchIR::at(Label(0)) - .nav(Nav::Down) - .neg_fields(0..10) - .next(Label(1)) - .into()], + instructions: vec![ + MatchIR::at(Label(0)) + .nav(Nav::Down) + .neg_fields(0..10) + .next(Label(1)) + .into(), + ], def_entries: Default::default(), preamble_entry: Label(0), }; @@ -146,13 +154,15 @@ fn lower_successors_overflow() { #[test] fn lower_combined_overflow() { let mut result = CompileResult { - instructions: vec![MatchIR::at(Label(0)) - .nav(Nav::Down) - .pre_effects((0..10).map(make_effect)) - .post_effects((0..10).map(make_effect)) - .neg_fields(0..10) - .next(Label(1)) - .into()], + instructions: vec![ + MatchIR::at(Label(0)) + .nav(Nav::Down) + .pre_effects((0..10).map(make_effect)) + .post_effects((0..10).map(make_effect)) + .neg_fields(0..10) + .next(Label(1)) + .into(), + ], def_entries: Default::default(), preamble_entry: Label(0), }; diff --git a/crates/plotnik-compiler/src/emit/emit_tests.rs b/crates/plotnik-compiler/src/emit/emit_tests.rs index 1cd95c5..203355c 100644 --- a/crates/plotnik-compiler/src/emit/emit_tests.rs +++ b/crates/plotnik-compiler/src/emit/emit_tests.rs @@ -9,454 +9,571 @@ use crate::shot_bytecode; #[test] fn nodes_named() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (identifier) @id - "#); + "# + ); } #[test] fn nodes_anonymous() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (binary_expression "+" @op) - "#); + "# + ); } #[test] fn nodes_wildcard_any() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (pair key: _ @key) - "#); + "# + ); } #[test] fn nodes_wildcard_named() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (pair key: (_) @key) - "#); + "# + ); } #[test] fn nodes_error() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (ERROR) @err - "#); + "# + ); } #[test] fn nodes_missing() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (MISSING) @m - "#); + "# + ); } // Captures #[test] fn captures_basic() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (identifier) @name - "#); + "# + ); } #[test] fn captures_multiple() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (binary_expression (identifier) @a (number) @b) - "#); + "# + ); } #[test] fn captures_nested_flat() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array (array (identifier) @c) @b) @a - "#); + "# + ); } #[test] fn captures_deeply_nested() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array (array (array (identifier) @d) @c) @b) @a - "#); + "# + ); } #[test] fn captures_with_type_string() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (identifier) @name :: string - "#); + "# + ); } #[test] fn captures_with_type_custom() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (identifier) @name :: Identifier - "#); + "# + ); } #[test] fn captures_struct_scope() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = {(identifier) @a (number) @b} @item - "#); + "# + ); } #[test] fn captures_wrapper_struct() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = {{(identifier) @id (number) @num} @row}* @rows - "#); + "# + ); } #[test] fn captures_optional_wrapper_struct() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = {{(identifier) @id} @inner}? @outer - "#); + "# + ); } #[test] fn captures_struct_with_type_annotation() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = {(identifier) @fn} @outer :: FunctionInfo - "#); + "# + ); } #[test] fn captures_enum_with_type_annotation() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = [A: (identifier) @id B: (number) @num] @expr :: Expression - "#); + "# + ); } // Fields #[test] fn fields_single() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (function_declaration name: (identifier) @name) - "#); + "# + ); } #[test] fn fields_multiple() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (binary_expression left: (_) @left right: (_) @right) - "#); + "# + ); } #[test] fn fields_negated() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (pair key: (property_identifier) @key -value) - "#); + "# + ); } #[test] fn fields_alternation() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (call_expression function: [(identifier) @fn (number) @num]) - "#); + "# + ); } // Quantifiers #[test] fn quantifiers_optional() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (function_declaration (decorator)? @dec) - "#); + "# + ); } #[test] fn quantifiers_star() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (identifier)* @items - "#); + "# + ); } #[test] fn quantifiers_plus() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (identifier)+ @items - "#); + "# + ); } #[test] fn quantifiers_optional_nongreedy() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (function_declaration (decorator)?? @dec) - "#); + "# + ); } #[test] fn quantifiers_star_nongreedy() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (identifier)*? @items - "#); + "# + ); } #[test] fn quantifiers_plus_nongreedy() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (identifier)+? @items - "#); + "# + ); } #[test] fn quantifiers_struct_array() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array {(identifier) @a (number) @b}* @items) - "#); + "# + ); } #[test] fn quantifiers_first_child_array() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array (identifier)* @ids (number) @n) - "#); + "# + ); } #[test] fn quantifiers_repeat_navigation() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (function_declaration (decorator)* @decs) - "#); + "# + ); } #[test] fn quantifiers_sequence_in_called_def() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Item = (identifier) @name Collect = {(Item) @item}* @items Test = (array (Collect)) - "#); + "# + ); } // Sequences #[test] fn sequences_basic() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array {(identifier) (number)}) - "#); + "# + ); } #[test] fn sequences_with_captures() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array {(identifier) @a (number) @b}) - "#); + "# + ); } #[test] fn sequences_nested() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array {(identifier) {(number) (string)} (null)}) - "#); + "# + ); } #[test] fn sequences_in_quantifier() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array {(identifier) @id (number) @num}* @items) - "#); + "# + ); } // Alternations #[test] fn alternations_unlabeled() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = [(identifier) @id (string) @str] - "#); + "# + ); } #[test] fn alternations_labeled() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = [ A: (identifier) @a B: (number) @b ] - "#); + "# + ); } #[test] fn alternations_null_injection() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = [(identifier) @x (number) @y] - "#); + "# + ); } #[test] fn alternations_captured() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = [(identifier) (number)] @value - "#); + "# + ); } #[test] fn alternations_captured_tagged() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = [A: (identifier) @a B: (number) @b] @item - "#); + "# + ); } #[test] fn alternations_tagged_with_definition_ref() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Inner = (identifier) @name Test = [A: (Inner) B: (number) @b] @item - "#); + "# + ); } #[test] fn alternations_in_quantifier() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (object { [A: (pair) @a B: (shorthand_property_identifier) @b] @item }* @items) - "#); + "# + ); } #[test] fn alternations_no_internal_captures() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (program [(identifier) (number)] @x) - "#); + "# + ); } #[test] fn alternations_tagged_in_field_constraint() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (pair key: [A: (identifier) @a B: (number)] @kind) - "#); + "# + ); } // Anchors #[test] fn anchors_between_siblings() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array (identifier) . (number)) - "#); + "# + ); } #[test] fn anchors_first_child() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array . (identifier)) - "#); + "# + ); } #[test] fn anchors_last_child() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array (identifier) .) - "#); + "# + ); } #[test] fn anchors_with_anonymous() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (binary_expression "+" . (identifier)) - "#); + "# + ); } #[test] fn anchors_no_anchor() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (array (identifier) (number)) - "#); + "# + ); } // Named expressions #[test] fn definitions_single() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Foo = (identifier) @id - "#); + "# + ); } #[test] fn definitions_multiple() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Foo = (identifier) @id Bar = (string) @str - "#); + "# + ); } #[test] fn definitions_reference() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Expression = [(identifier) @name (number) @value] Root = (function_declaration name: (identifier) @name) - "#); + "# + ); } #[test] fn definitions_nested_capture() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Inner = (call_expression (identifier) @name) Outer = (array {(Inner) @item}* @items) - "#); + "# + ); } // Recursion #[test] fn recursion_simple() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Expr = [ Lit: (number) @value :: string Rec: (call_expression function: (identifier) @fn arguments: (Expr) @inner) ] - "#); + "# + ); } #[test] fn recursion_with_structured_result() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Expr = [ Lit: (number) @value :: string Nested: (call_expression function: (identifier) @fn arguments: (Expr) @inner) ] Test = (program (Expr) @expr) - "#); + "# + ); } // Optionals #[test] fn optional_first_child() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (program (identifier)? @id (number) @n) - "#); + "# + ); } #[test] fn optional_null_injection() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = (function_declaration (decorator)? @dec) - "#); + "# + ); } // Optimization: prefix collapse #[test] fn opt_prefix_collapse() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Test = [(object (pair)) (object (string))] - "#); + "# + ); } // Comprehensive #[test] fn comprehensive_multi_definition() { - shot_bytecode!(r#" + shot_bytecode!( + r#" Ident = (identifier) @name :: string Expression = [ Literal: (number) @value @@ -465,5 +582,6 @@ fn comprehensive_multi_definition() { Assignment = (assignment_expression left: (identifier) @target right: (Expression) @value) - "#); + "# + ); } diff --git a/crates/plotnik-compiler/src/emit/layout.rs b/crates/plotnik-compiler/src/emit/layout.rs index a1105bc..9ef4144 100644 --- a/crates/plotnik-compiler/src/emit/layout.rs +++ b/crates/plotnik-compiler/src/emit/layout.rs @@ -141,7 +141,10 @@ impl BlockRefs { } fn count(&self, from_block: usize, to_block: usize) -> usize { - self.direct.get(&(from_block, to_block)).copied().unwrap_or(0) + self.direct + .get(&(from_block, to_block)) + .copied() + .unwrap_or(0) } fn predecessors(&self, block: usize) -> &[usize] { @@ -271,10 +274,7 @@ fn build_layout_ir( } /// Build block reference counts from current layout. -fn build_block_refs( - ir: &LayoutIR, - label_to_instr: &BTreeMap, -) -> BlockRefs { +fn build_block_refs(ir: &LayoutIR, label_to_instr: &BTreeMap) -> BlockRefs { let mut refs = BlockRefs::new(); for (&label, &block_idx) in &ir.label_to_block { @@ -344,7 +344,9 @@ fn pack_successors( .max_by(|&a, &b| { let score_a = block_score(pred_block, a, refs); let score_b = block_score(pred_block, b, refs); - score_a.partial_cmp(&score_b).unwrap_or(std::cmp::Ordering::Equal) + score_a + .partial_cmp(&score_b) + .unwrap_or(std::cmp::Ordering::Equal) }); if let Some(candidate) = best { @@ -422,4 +424,3 @@ fn order_chains(mut chains: Vec>, entries: &[Label]) -> Vec Lang { paste::paste! { - static LANG: LazyLock = LazyLock::new(|| { + static LANG: std::sync::LazyLock = std::sync::LazyLock::new(|| { static NODE_TYPES_BYTES: &[u8] = include_bytes!(env!( concat!("PLOTNIK_NODE_TYPES_", $lang_key) )); @@ -46,7 +44,7 @@ macro_rules! define_langs { let grammar = plotnik_core::grammar::Grammar::from_binary(GRAMMAR_BYTES) .expect("invalid embedded grammar"); - Arc::new(LangInner::new( + std::sync::Arc::new(crate::LangInner::new( $name, $ts_lang.into(), raw_nodes, @@ -54,7 +52,7 @@ macro_rules! define_langs { )) }); } - Arc::clone(&LANG) + std::sync::Arc::clone(&LANG) } )* diff --git a/crates/plotnik-lib/Cargo.toml b/crates/plotnik-lib/Cargo.toml index 0e43d8c..0c5bf0a 100644 --- a/crates/plotnik-lib/Cargo.toml +++ b/crates/plotnik-lib/Cargo.toml @@ -15,15 +15,15 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } [dependencies] plotnik-bytecode.workspace = true -plotnik-compiler.workspace = true +plotnik-compiler = { path = "../plotnik-compiler", version = "0.3.3", default-features = false } plotnik-core.workspace = true plotnik-vm.workspace = true -plotnik-langs = { workspace = true, optional = true } arborium-tree-sitter = "2.12.4" serde = { version = "1.0", features = ["derive"] } [features] default = ["plotnik-langs"] +plotnik-langs = ["plotnik-compiler/plotnik-langs"] [dev-dependencies] insta = { version = "=1.46.1", features = ["yaml"] } diff --git a/docs/lang-reference.md b/docs/lang-reference.md index edd3ad1..8458c4d 100644 --- a/docs/lang-reference.md +++ b/docs/lang-reference.md @@ -385,15 +385,15 @@ Filter nodes by their text content with inline predicates: (identifier !~ /^_/) ; text does not match regex ``` -| Operator | Meaning | -| -------- | ----------------- | -| `==` | equals | -| `!=` | not equals | -| `^=` | starts with | -| `$=` | ends with | -| `*=` | contains | -| `=~` | matches regex | -| `!~` | does not match | +| Operator | Meaning | +| -------- | -------------- | +| `==` | equals | +| `!=` | not equals | +| `^=` | starts with | +| `$=` | ends with | +| `*=` | contains | +| `=~` | matches regex | +| `!~` | does not match | **Regex patterns** use `/pattern/` syntax. Full Unicode is supported. Patterns match anywhere in the text (use `^` and `$` anchors for full-match semantics). @@ -404,6 +404,7 @@ Filter nodes by their text content with inline predicates: ``` **Unsupported regex features** (compile-time error): + - Backreferences (`\1`, `\2`) - Lookahead/lookbehind (`(?=...)`, `(?!...)`, `(?<=...)`, `(?...)`) @@ -977,28 +978,28 @@ type Root = { ## Quick Reference -| Feature | Tree-sitter | Plotnik | -| -------------------- | ------------------ | ----------------------------- | -| Capture | `@name` | `@name` (snake_case only) | -| Suppressive capture | | `@_` or `@_name` | -| Type annotation | | `@x :: T` | -| Text extraction | | `@x :: string` | -| Named node | `(type)` | `(type)` | -| Anonymous node | `"text"` | `"text"` | -| Any node | `_` | `_` | -| Any named node | `(_)` | `(_)` | -| Field constraint | `field: pattern` | `field: pattern` | -| Negated field | `!field` | `-field` | -| Quantifiers | `?` `*` `+` | `?` `*` `+` | -| Non-greedy | | `??` `*?` `+?` | -| Sequence | `((a) (b))` | `{(a) (b)}` | -| Alternation | `[a b]` | `[a b]` | -| Tagged alternation | | `[A: (a) B: (b)]` | -| Anchor | `.` | `.` | -| Predicate | `(#eq? @x "foo")` | `(node == "foo")` | -| Regex predicate | `(#match? @x "p")` | `(node =~ /p/)` | -| Named expression | | `Name = pattern` | -| Use named expression | | `(Name)` | +| Feature | Tree-sitter | Plotnik | +| -------------------- | ------------------ | ------------------------- | +| Capture | `@name` | `@name` (snake_case only) | +| Suppressive capture | | `@_` or `@_name` | +| Type annotation | | `@x :: T` | +| Text extraction | | `@x :: string` | +| Named node | `(type)` | `(type)` | +| Anonymous node | `"text"` | `"text"` | +| Any node | `_` | `_` | +| Any named node | `(_)` | `(_)` | +| Field constraint | `field: pattern` | `field: pattern` | +| Negated field | `!field` | `-field` | +| Quantifiers | `?` `*` `+` | `?` `*` `+` | +| Non-greedy | | `??` `*?` `+?` | +| Sequence | `((a) (b))` | `{(a) (b)}` | +| Alternation | `[a b]` | `[a b]` | +| Tagged alternation | | `[A: (a) B: (b)]` | +| Anchor | `.` | `.` | +| Predicate | `(#eq? @x "foo")` | `(node == "foo")` | +| Regex predicate | `(#match? @x "p")` | `(node =~ /p/)` | +| Named expression | | `Name = pattern` | +| Use named expression | | `(Name)` | ---