33//! This module provides automatic correction of missing or mismatched
44//! parentheses in OVSM code, similar to error recovery in modern compilers.
55
6- use crate :: error:: { Error , Result } ;
6+ use crate :: error:: Result ;
7+ use crate :: Scanner ;
8+ use crate :: parser:: SExprParser ;
79
810/// Attempts to automatically fix missing or mismatched parentheses
911pub struct ParenFixer {
@@ -22,7 +24,7 @@ impl ParenFixer {
2224 Self { source, lines }
2325 }
2426
25- /// Analyze and fix parenthesis imbalances
27+ /// Analyze and fix parenthesis imbalances with smart iterative placement
2628 pub fn fix ( & self ) -> Result < String > {
2729 // Count parentheses
2830 let stats = self . count_parens ( ) ;
@@ -32,18 +34,132 @@ impl ParenFixer {
3234 return Ok ( self . source . clone ( ) ) ;
3335 }
3436
35- // Try to fix the imbalance
37+ // Try to fix the imbalance with smart placement
3638 if stats. open_count > stats. close_count {
37- // Missing closing parens - add them at the end
39+ // Missing closing parens - try smart placement
3840 let missing = stats. open_count - stats. close_count ;
41+
42+ // Try smart iterative placement first
43+ if let Some ( fixed) = self . smart_fix_missing_parens ( missing) {
44+ return Ok ( fixed) ;
45+ }
46+
47+ // Fallback to simple end placement
3948 Ok ( self . add_closing_parens ( missing) )
4049 } else {
41- // Too many closing parens - remove extras or add opening
50+ // Too many closing parens - remove extras
4251 let extra = stats. close_count - stats. open_count ;
4352 Ok ( self . remove_extra_closing_parens ( extra) )
4453 }
4554 }
4655
56+ /// Try to fix missing parens by iteratively trying different positions
57+ /// Returns the first valid placement that parses successfully
58+ fn smart_fix_missing_parens ( & self , missing : usize ) -> Option < String > {
59+ // Find candidate positions where we can insert closing parens
60+ let candidates = self . find_insertion_candidates ( ) ;
61+
62+ // Try each candidate position
63+ for pos in candidates {
64+ let attempt = self . insert_closing_parens_at ( pos, missing) ;
65+
66+ // Validate by parsing
67+ if self . validates ( & attempt) {
68+ return Some ( attempt) ;
69+ }
70+ }
71+
72+ None
73+ }
74+
75+ /// Find candidate positions for inserting closing parens
76+ /// Returns positions sorted by likelihood of being correct
77+ fn find_insertion_candidates ( & self ) -> Vec < usize > {
78+ let mut candidates = Vec :: new ( ) ;
79+ let mut in_string = false ;
80+ let mut escape_next = false ;
81+ let mut depth = 0_i32 ;
82+
83+ for ( idx, ch) in self . source . chars ( ) . enumerate ( ) {
84+ // Handle escape sequences
85+ if escape_next {
86+ escape_next = false ;
87+ continue ;
88+ }
89+
90+ if ch == '\\' && in_string {
91+ escape_next = true ;
92+ continue ;
93+ }
94+
95+ // Handle strings
96+ if ch == '"' {
97+ in_string = !in_string;
98+ continue ;
99+ }
100+
101+ if in_string {
102+ continue ;
103+ }
104+
105+ // Track depth and record positions
106+ match ch {
107+ '(' => {
108+ depth += 1 ;
109+ }
110+ ')' => {
111+ depth -= 1 ;
112+ }
113+ '\n' => {
114+ // End of line is a good candidate if we're in nested code
115+ if depth > 0 {
116+ candidates. push ( idx + 1 ) ;
117+ }
118+ }
119+ _ => { }
120+ }
121+ }
122+
123+ // Add end of file as final candidate
124+ candidates. push ( self . source . len ( ) ) ;
125+
126+ // Sort by likelihood (prefer positions with higher depth changes)
127+ // For now, keep them in order (end of lines, then end of file)
128+ candidates
129+ }
130+
131+ /// Insert closing parens at a specific position
132+ fn insert_closing_parens_at ( & self , pos : usize , count : usize ) -> String {
133+ let mut result = String :: new ( ) ;
134+ result. push_str ( & self . source [ ..pos] ) ;
135+
136+ // Add the closing parens
137+ for _ in 0 ..count {
138+ result. push ( ')' ) ;
139+ }
140+
141+ // Add rest of source
142+ if pos < self . source . len ( ) {
143+ result. push_str ( & self . source [ pos..] ) ;
144+ }
145+
146+ result
147+ }
148+
149+ /// Validate code by attempting to parse it
150+ fn validates ( & self , code : & str ) -> bool {
151+ // Try to tokenize
152+ let mut scanner = Scanner :: new ( code) ;
153+ let tokens = match scanner. scan_tokens ( ) {
154+ Ok ( t) => t,
155+ Err ( _) => return false ,
156+ } ;
157+
158+ // Try to parse
159+ let mut parser = SExprParser :: new ( tokens) ;
160+ parser. parse ( ) . is_ok ( )
161+ }
162+
47163 /// Count all parentheses in the source
48164 fn count_parens ( & self ) -> ParenStats {
49165 let mut open_count = 0 ;
@@ -195,13 +311,32 @@ impl ParenFixer {
195311 return ( self . source . clone ( ) , None ) ;
196312 }
197313
314+ // Try to fix and determine which method was used
315+ let fixed_result = self . fix ( ) ;
316+
198317 let report = if stats. open_count > stats. close_count {
199318 let missing = stats. open_count - stats. close_count ;
200- Some ( format ! (
201- "⚠️ Auto-corrected: Added {} missing closing paren{} at end of code" ,
202- missing,
203- if missing == 1 { "" } else { "s" }
204- ) )
319+
320+ // Check if smart placement found a solution
321+ let used_smart = if let Ok ( ref _fixed) = fixed_result {
322+ self . smart_fix_missing_parens ( missing) . is_some ( )
323+ } else {
324+ false
325+ } ;
326+
327+ if used_smart {
328+ Some ( format ! (
329+ "✨ Auto-corrected: Added {} missing closing paren{} (smart placement validated)" ,
330+ missing,
331+ if missing == 1 { "" } else { "s" }
332+ ) )
333+ } else {
334+ Some ( format ! (
335+ "⚠️ Auto-corrected: Added {} missing closing paren{} at end of code" ,
336+ missing,
337+ if missing == 1 { "" } else { "s" }
338+ ) )
339+ }
205340 } else {
206341 let extra = stats. close_count - stats. open_count ;
207342 Some ( format ! (
@@ -211,7 +346,7 @@ impl ParenFixer {
211346 ) )
212347 } ;
213348
214- match self . fix ( ) {
349+ match fixed_result {
215350 Ok ( fixed) => ( fixed, report) ,
216351 Err ( _) => ( self . source . clone ( ) , Some ( "⚠️ Could not auto-correct parentheses" . to_string ( ) ) ) ,
217352 }
@@ -253,9 +388,10 @@ mod tests {
253388 let ( fixed, report) = fixer. fix_with_report ( ) ;
254389
255390 assert ! ( fixed. contains( ')' ) ) ;
256- assert ! ( fixed. contains( "Auto-corrected" ) ) ;
257391 assert ! ( report. is_some( ) ) ;
258- assert ! ( report. unwrap( ) . contains( "1 missing closing paren" ) ) ;
392+ let msg = report. unwrap ( ) ;
393+ assert ! ( msg. contains( "Auto-corrected" ) ) ;
394+ assert ! ( msg. contains( "1 missing closing paren" ) ) ;
259395 }
260396
261397 #[ test]
@@ -349,4 +485,47 @@ mod tests {
349485 assert_eq ! ( fixed, code) ;
350486 assert ! ( report. is_none( ) ) ;
351487 }
488+
489+ #[ test]
490+ fn test_smart_placement_validation ( ) {
491+ // Code with missing paren that SHOULD be placed mid-code, not at end
492+ let code = "(define x (+ 1 2)\n (define y (+ 3 4))" ;
493+
494+ let fixer = ParenFixer :: new ( code) ;
495+ let ( fixed, report) = fixer. fix_with_report ( ) ;
496+
497+ // Should be fixed and parseable
498+ assert ! ( report. is_some( ) ) ;
499+
500+ // Verify it actually parses
501+ let validates = fixer. validates ( & fixed) ;
502+ assert ! ( validates, "Smart placement should produce parseable code" ) ;
503+
504+ // Should use smart placement (indicated by ✨)
505+ if let Some ( msg) = report {
506+ // Either smart placement (✨) or fallback (⚠️) - both should work
507+ assert ! ( msg. contains( "Auto-corrected" ) ) ;
508+ }
509+ }
510+
511+ #[ test]
512+ fn test_smart_placement_finds_correct_position ( ) {
513+ // Code with missing paren where smart placement should help
514+ let code = "(define data [1 2 3]\n (define count (+ 1 2)" ;
515+
516+ let fixer = ParenFixer :: new ( code) ;
517+ let ( fixed, report) = fixer. fix_with_report ( ) ;
518+
519+ // Should fix it
520+ assert ! ( report. is_some( ) ) ;
521+
522+ // Count should be balanced
523+ let open = fixed. chars ( ) . filter ( |& c| c == '(' ) . count ( ) ;
524+ let close = fixed. chars ( ) . filter ( |& c| c == ')' ) . count ( ) ;
525+ assert_eq ! ( open, close, "Parentheses should be balanced" ) ;
526+
527+ // Ideally the fixed code should parse (best effort)
528+ // Note: Smart placement may not always find the perfect spot,
529+ // but it should at least balance the parens
530+ }
352531}
0 commit comments