Skip to main content

perl_dap/
lib.rs

1//! Debug Adapter Protocol Implementation for Perl
2//!
3//! This crate provides a production-grade Debug Adapter Protocol (DAP) server for Perl,
4//! enabling debugging support in VSCode, Neovim, Emacs, and other DAP-compatible editors.
5//!
6//! The adapter integrates with `perl_parser` for AST-based breakpoint validation and
7//! leverages existing LSP infrastructure for position mapping and workspace navigation.
8//!
9//! # Features
10//!
11//! - **Native Runtime**: Current launch/attach implementation for Perl debugging sessions
12//! - **Launch Debugging**: Start and debug Perl processes with full control
13//! - **Attach Debugging**: Attach to running Perl processes via TCP
14//! - **Legacy Bridge Mode**: Compatibility proxy for Perl::LanguageServer installations
15//! - **AST-Based Validation**: Breakpoint validation using parsed syntax trees
16//! - **Cross-Platform**: Windows, macOS, and Linux support with path normalization
17//! - **Configuration Snippets**: VSCode launch.json generation
18//!
19//! # Quick Start
20//!
21//! ## Native and Bridge Modes
22//!
23//! Native launch and attach are the current default runtime paths for new sessions.
24//! The legacy bridge adapter remains available when an installation still needs to
25//! proxy DAP messages to Perl::LanguageServer:
26//!
27//! ```no_run
28//! use perl_dap::BridgeAdapter;
29//!
30//! # #[tokio::main]
31//! # async fn main() -> anyhow::Result<()> {
32//! let mut adapter = BridgeAdapter::new();
33//!
34//! // Start Perl::LanguageServer DAP backend
35//! adapter.spawn_pls_dap().await?;
36//!
37//! // Proxy messages between VSCode and PLS
38//! adapter.proxy_messages().await?;
39//!
40//! // Cleanup on shutdown
41//! adapter.shutdown().await?;
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! ## Launch Configuration
47//!
48//! Create debugging configurations for launching Perl scripts:
49//!
50//! ```no_run
51//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
52//! use perl_dap::LaunchConfiguration;
53//! use std::path::PathBuf;
54//! use std::collections::HashMap;
55//!
56//! let config = LaunchConfiguration {
57//!     program: PathBuf::from("script.pl"),
58//!     args: vec!["--verbose".to_string()],
59//!     cwd: Some(PathBuf::from("/workspace")),
60//!     env: HashMap::new(),
61//!     perl_path: None,
62//!     include_paths: vec![PathBuf::from("lib")],
63//! };
64//!
65//! // Validate configuration before launching
66//! config.validate()?;
67//! # Ok::<(), Box<dyn std::error::Error>>(())
68//! # }
69//! ```
70//!
71//! ## Attach Configuration
72//!
73//! Attach to running Perl processes via TCP:
74//!
75//! ```rust
76//! use perl_dap::AttachConfiguration;
77//!
78//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
79//! let config = AttachConfiguration {
80//!     host: "localhost".to_string(),
81//!     port: 13603,
82//!     timeout_ms: Some(5000),
83//!     stop_on_entry: None,
84//! };
85//!
86//! config.validate()?;
87//! # Ok(())
88//! # }
89//! ```
90//!
91//! ## VSCode Integration
92//!
93//! Generate launch.json snippets for VSCode:
94//!
95//! ```rust
96//! use perl_dap::{create_launch_json_snippet, create_attach_json_snippet};
97//!
98//! // Generate launch configuration snippet
99//! let launch_snippet = create_launch_json_snippet();
100//! println!("{}", launch_snippet);
101//!
102//! // Generate attach configuration snippet
103//! let attach_snippet = create_attach_json_snippet();
104//! println!("{}", attach_snippet);
105//! ```
106//!
107//! # Architecture
108//!
109//! `perl-dap` exposes a dual architecture that supports both current runtime use
110//! and compatibility migration:
111//!
112//! - **Native runtime (`DapServer` + `DebugAdapter`)** handles launch/attach
113//!   flows, request dispatch, breakpoint/state management, and variable/evaluate
114//!   inspection paths.
115//! - **Legacy bridge (`BridgeAdapter`)** proxies traffic to
116//!   `Perl::LanguageServer` for compatibility when teams are still migrating.
117//! - **Shared foundations** (`protocol`, `dispatcher`, `breakpoints`,
118//!   `platform`) provide message contracts, routing, validation, and
119//!   cross-platform process setup.
120//!
121//! This keeps one crate boundary for editor integrations while allowing
122//! per-session selection between native execution and bridge fallback behavior.
123//!
124//! # Protocol Support
125//!
126//! The adapter implements DAP 1.51+ specification features:
127//!
128//! ## Initialization
129//!
130//! - `initialize` - Capability negotiation
131//! - `attach` / `launch` - Debug session start
132//! - `configurationDone` - Initialization complete
133//! - `disconnect` - Session termination
134//!
135//! ## Breakpoints
136//!
137//! - `setBreakpoints` - Set breakpoints with AST validation
138//! - `setFunctionBreakpoints` - Break on function entry
139//! - `setExceptionBreakpoints` - Break on exceptions
140//!
141//! ## Execution Control
142//!
143//! - `continue` - Resume execution
144//! - `next` - Step over
145//! - `stepIn` - Step into
146//! - `stepOut` - Step out
147//! - `pause` - Pause execution
148//!
149//! ## Inspection
150//!
151//! - `threads` - List active threads
152//! - `stackTrace` - Get call stack
153//! - `scopes` - Get variable scopes
154//! - `variables` - Inspect variables with lazy loading
155//! - `evaluate` - Evaluate expressions in context
156//!
157//! # Breakpoint Validation
158//!
159//! The [`breakpoints`] module provides AST-based validation:
160//!
161//! ```rust,ignore
162//! use perl_dap::{BreakpointStore, SourceBreakpoint};
163//! use perl_parser::Parser;
164//!
165//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
166//! let code = "sub foo {\n    my $x = 1;\n    return $x;\n}";
167//! let mut parser = Parser::new(code);
168//! let ast = parser.parse()?;
169//!
170//! let mut store = BreakpointStore::new();
171//! let bp = SourceBreakpoint {
172//!     line: 2,
173//!     column: None,
174//!     condition: None,
175//!     hit_condition: None,
176//!     log_message: None,
177//! };
178//!
179//! // Validate breakpoint is on executable line
180//! let validated = store.add_breakpoint("script.pl", bp, &ast);
181//! # Ok(())
182//! # }
183//! ```
184//!
185//! # Platform Support
186//!
187//! The [`platform`] module handles cross-platform concerns:
188//!
189//! - **Path Resolution**: Normalize paths for Windows/Unix
190//! - **Perl Discovery**: Find Perl interpreter in PATH
191//! - **Environment Setup**: Configure @INC and environment variables
192//! - **Process Spawning**: Launch Perl processes with proper stdio handling
193//!
194//! ```rust,ignore
195//! use perl_dap::platform::{find_perl, normalize_path};
196//!
197//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
198//! let perl = find_perl().unwrap_or_else(|| std::path::PathBuf::from("/usr/bin/perl"));
199//! let normalized = normalize_path("/workspace/lib/Foo.pm");
200//! # Ok(())
201//! # }
202//! ```
203//!
204//! # Configuration Examples
205//!
206//! ## VSCode launch.json
207//!
208//! ```json
209//! {
210//!   "version": "0.2.0",
211//!   "configurations": [
212//!     {
213//!       "type": "perl",
214//!       "request": "launch",
215//!       "name": "Debug Perl Script",
216//!       "program": "${workspaceFolder}/script.pl",
217//!       "args": ["--verbose"],
218//!       "cwd": "${workspaceFolder}",
219//!       "includePaths": ["lib", "local/lib/perl5"]
220//!     },
221//!     {
222//!       "type": "perl",
223//!       "request": "attach",
224//!       "name": "Attach to Perl",
225//!       "host": "localhost",
226//!       "port": 13603
227//!     }
228//!   ]
229//! }
230//! ```
231//!
232//! ## Programmatic Configuration
233//!
234//! ```rust
235//! use perl_dap::LaunchConfiguration;
236//! use std::path::PathBuf;
237//! use std::collections::HashMap;
238//!
239//! let mut env = HashMap::new();
240//! env.insert("PERL5LIB".to_string(), "lib:local/lib/perl5".to_string());
241//!
242//! let config = LaunchConfiguration {
243//!     program: PathBuf::from("${workspaceFolder}/script.pl"),
244//!     args: vec!["--debug".to_string()],
245//!     cwd: Some(PathBuf::from("${workspaceFolder}")),
246//!     env,
247//!     perl_path: Some(PathBuf::from("/usr/bin/perl")),
248//!     include_paths: vec![
249//!         PathBuf::from("lib"),
250//!         PathBuf::from("local/lib/perl5"),
251//!     ],
252//! };
253//! ```
254//!
255//! # Testing
256//!
257//! The adapter includes comprehensive test coverage:
258//!
259//! ```bash
260//! # Run all DAP tests
261//! cargo test -p perl-dap
262//!
263//! # Test specific phase
264//! cargo test -p perl-dap bridge_adapter
265//!
266//! # Integration tests
267//! cargo test -p perl-dap --test integration_tests
268//! ```
269//!
270//! All tests are tagged with acceptance criteria (AC1-AC19) for traceability:
271//!
272//! ```rust,ignore
273//! #[test]
274//! fn test_launch_config_validation() {
275//!     // AC:2 - Launch configuration validation
276//!     let config = LaunchConfiguration { /* ... */ };
277//!     assert!(config.validate().is_ok());
278//! }
279//! ```
280//!
281//! # Security Considerations
282//!
283//! - **Command Injection**: All paths and arguments are sanitized
284//! - **Arbitrary Execution**: Evaluation restricted to debug context
285//! - **Resource Limits**: Memory and time budgets for operations
286//! - **Path Validation**: Prevent directory traversal and unauthorized access
287//!
288//! # Error Handling
289//!
290//! The adapter uses `anyhow::Result` for comprehensive error reporting:
291//!
292//! ```rust,ignore
293//! use perl_dap::{BridgeAdapter, DapError};
294//!
295//! async fn run_adapter() -> anyhow::Result<()> {
296//!     let mut adapter = BridgeAdapter::new();
297//!     adapter.spawn_pls_dap().await?;
298//!     adapter.proxy_messages().await?;
299//!     Ok(())
300//! }
301//! ```
302//!
303//! # Integration with perl-parser
304//!
305//! The DAP adapter leverages `perl_parser` for:
306//!
307//! - **Breakpoint Validation**: Verify breakpoints on executable lines
308//! - **Variable Inspection**: Type-aware variable rendering
309//! - **Expression Evaluation**: Parse and evaluate debug expressions
310//! - **Source Mapping**: Map positions between editor and runtime
311//!
312//! # Migration Path
313//!
314//! Phase 1 provides immediate value via bridging, while Phase 2 and 3 will gradually
315//! migrate functionality to native Rust implementation for better performance and
316//! integration.
317//!
318//! Users can start with bridge mode today and transparently upgrade to native mode
319//! when Phase 2 is complete.
320//!
321//! # Related Crates
322//!
323//! - `perl_parser`: Parsing engine and AST analysis
324//! - `perl_lsp`: Language Server Protocol implementation
325//! - `perl_lexer`: Context-aware Perl tokenizer
326//!
327//! # Documentation
328//!
329//! - **DAP Specification**: [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)
330//! - **Implementation Guide**: See `docs/DAP_IMPLEMENTATION_GUIDE.md`
331//! - **Issue Tracking**: See GitHub issue #207 for acceptance criteria
332//!
333//! # Test-Driven Development
334//!
335//! This crate follows TDD principles with acceptance criteria from Issue #207.
336//! All tests are tagged with `// AC:ID` comments for traceability to specifications.
337
338#![warn(missing_docs)]
339#![cfg_attr(test, allow(clippy::print_stderr, clippy::print_stdout))]
340
341// Phase 1 modules (AC1-AC4) - IMPLEMENTED
342/// Bridge adapter for communicating with Perl::LanguageServer's DAP implementation.
343pub mod bridge_adapter;
344/// Launch and attach configuration structures for DAP debugging sessions.
345pub mod configuration;
346/// Debug Adapter Protocol (DAP) implementation for Perl debugging.
347pub mod debug_adapter;
348/// DAP feature catalog and capability gating helpers.
349pub mod feature_catalog;
350
351// Wave H collapsed modules (in DAG order — command_args before platform, platform before shell, value before variables)
352/// Explicit public API re-exports from all collapsed satellite modules.
353pub mod api;
354/// AST-based breakpoint validation (from perl-dap-breakpoint).
355pub mod breakpoint;
356/// Platform-aware shell argument formatting (from perl-dap-command-args).
357pub mod command_args;
358/// DAP launch and attach configuration types (from perl-dap-config).
359pub mod config;
360/// Safe expression evaluation validation (from perl-dap-eval).
361pub mod eval;
362/// Cross-platform utilities for Perl path resolution and environment setup (from perl-dap-platform).
363pub mod platform;
364/// Security validation and hardening (from perl-dap-security).
365pub mod security;
366/// Shell-specific helpers for Perl DAP process launch (from perl-dap-shell).
367pub mod shell;
368/// Stack trace parsing and frame classification (from perl-dap-stack).
369pub mod stack;
370/// Shared DAP session model types (from perl-dap-types).
371pub mod types;
372/// Shared Perl value model for DAP parser and renderer (from perl-dap-value).
373pub mod value;
374/// Variable parsing and rendering for Perl DAP (from perl-dap-variables).
375pub mod variables;
376
377// Phase 2 modules (AC5-AC12) - IN PROGRESS
378/// Breakpoint storage and management for the DAP adapter.
379pub mod breakpoints;
380/// Inline value extraction for DAP `inlineValues` requests.
381pub mod inline_values;
382/// DAP protocol types following the JSON-RPC 2.0 message format.
383pub mod protocol;
384/// DAP server lifecycle, configuration, and operating mode.
385pub mod server;
386/// TCP-based attachment to running Perl debugger processes.
387pub mod tcp_attach;
388
389// Phase 2 modules (AC5-AC12) - Tracked in GitHub issues
390// See #449: Implement session management (AC5)
391// See #450: Implement AST-based breakpoint validation (AC7)
392// See #452: Implement variable renderer with lazy expansion (AC8)
393// See #453: Implement stack trace provider (AC8)
394// See #454: Implement control flow handlers (AC9)
395// See #455: Implement safe evaluation (AC10)
396
397/// Type-safe variablesReference codec — retiring the #1219 ID/ref-space collision class.
398pub mod var_ref {
399    pub use crate::debug_adapter::var_ref::{ScopeKind, VariableReference, VariableReferenceError};
400}
401
402// Re-export codec types at crate root for ergonomic use in tests and consumer crates.
403pub use debug_adapter::var_ref::{ScopeKind, VariableReference, VariableReferenceError};
404
405// Re-export Phase 1 public types
406pub use bridge_adapter::{BridgeAdapter, DapBridgeEnvConfig};
407pub use configuration::{
408    AttachConfiguration, LaunchConfiguration, create_attach_json_snippet,
409    create_launch_json_snippet,
410};
411pub use debug_adapter::{DapMessage, DebugAdapter};
412pub use server::{DapConfig, DapMode, DapServer};
413
414// Re-export Phase 2 public types
415pub use breakpoints::{BreakpointRecord, BreakpointStore, interpolate_logpoint_message};
416pub use protocol::{
417    AttachRequestArguments, Breakpoint, BreakpointLocation, BreakpointLocationsArguments,
418    BreakpointLocationsResponseBody, CancelArguments, Capabilities, CompletionItem,
419    CompletionsArguments, CompletionsResponseBody, ContinueArguments, ContinueResponseBody,
420    DataBreakpoint, DataBreakpointInfoArguments, DataBreakpointInfoResponseBody,
421    DisconnectArguments, EvaluateArguments, EvaluateResponseBody, Event, ExceptionBreakpointFilter,
422    ExceptionDetails, ExceptionFilterOption, ExceptionInfoArguments, ExceptionInfoResponseBody,
423    FunctionBreakpoint, GotoArguments, GotoTarget, GotoTargetsArguments, GotoTargetsResponseBody,
424    InitializeRequestArguments, LaunchRequestArguments, LoadedSourcesResponseBody, Module,
425    ModulesArguments, ModulesResponseBody, NextArguments, PauseArguments, ProtocolStackFrame,
426    ProtocolVariable, Request, Response, RestartArguments, RestartFrameArguments, Scope,
427    ScopesArguments, ScopesResponseBody, SetBreakpointsArguments, SetBreakpointsResponseBody,
428    SetDataBreakpointsArguments, SetDataBreakpointsResponseBody, SetExceptionBreakpointsArguments,
429    SetExpressionArguments, SetExpressionResponseBody, SetFunctionBreakpointsArguments,
430    SetVariableArguments, SetVariableResponseBody, Source, SourceArguments, SourceBreakpoint,
431    SourceResponseBody, StackTraceArguments, StackTraceResponseBody, StepInArguments, StepInTarget,
432    StepInTargetsArguments, StepInTargetsResponseBody, StepOutArguments, TerminateArguments,
433    TerminateThreadsArguments, Thread, ThreadsResponseBody, VariablesArguments,
434    VariablesResponseBody,
435};