File size: 5,227 Bytes
46df5f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
"""
Equation formatting checker.

Validates:
- Punctuation after equations (based on grammar)
- Equation numbering consistency
- Variable definitions
"""
import re
from typing import List, Set

from .base import BaseChecker, CheckResult, CheckSeverity


class EquationChecker(BaseChecker):
    """Check equation formatting and consistency."""
    
    name = "equation"
    display_name = "Equations"
    description = "Check equation formatting and punctuation"
    
    # Equation environments
    EQUATION_ENVS = [
        'equation', 'align', 'gather', 'multline', 'eqnarray',
        'equation*', 'align*', 'gather*', 'multline*', 'eqnarray*'
    ]
    
    def check(self, tex_content: str, config: dict = None) -> List[CheckResult]:
        results = []
        
        # Check equation punctuation
        punct_results = self._check_equation_punctuation(tex_content)
        results.extend(punct_results)
        
        # Check for numbered vs unnumbered consistency
        numbering_results = self._check_numbering_consistency(tex_content)
        results.extend(numbering_results)
        
        # Check inline math consistency ($...$ vs \(...\))
        inline_results = self._check_inline_math_consistency(tex_content)
        results.extend(inline_results)
        
        return results
    
    def _check_equation_punctuation(self, content: str) -> List[CheckResult]:
        """Check if equations end with appropriate punctuation."""
        results = []
        
        for env in self.EQUATION_ENVS:
            if '*' in env:
                env_escaped = env.replace('*', r'\*')
            else:
                env_escaped = env
            
            # Find equation content
            pattern = re.compile(
                rf'\\begin\{{{env_escaped}\}}(.*?)\\end\{{{env_escaped}\}}',
                re.DOTALL
            )
            
            for match in pattern.finditer(content):
                eq_content = match.group(1).strip()
                
                # Check what comes after the equation
                after_pos = match.end()
                after_text = content[after_pos:after_pos + 50].strip()
                
                # Equations in running text should have punctuation
                # Check if equation content ends with punctuation
                eq_content_clean = re.sub(r'\\label\{[^}]+\}', '', eq_content).strip()
                
                if eq_content_clean and not re.search(r'[.,;]$', eq_content_clean):
                    # Check if next text starts lowercase (indicating sentence continues)
                    if after_text and after_text[0].islower():
                        line_num = self._find_line_number(content, match.end())
                        results.append(self._create_result(
                            passed=False,
                            severity=CheckSeverity.INFO,
                            message="Equation may need punctuation (sentence continues after)",
                            line_number=line_num,
                            suggestion="Add comma or period inside equation if it ends a clause"
                        ))
        
        return results
    
    def _check_numbering_consistency(self, content: str) -> List[CheckResult]:
        """Check for mixed numbered and unnumbered equations."""
        results = []
        
        # Count numbered vs unnumbered
        numbered = 0
        unnumbered = 0
        
        for env in self.EQUATION_ENVS:
            count = len(re.findall(rf'\\begin\{{{env}\}}', content))
            if '*' in env or 'nonumber' in content:
                unnumbered += count
            else:
                numbered += count
        
        # Also count \nonumber and \notag usage
        unnumbered += len(re.findall(r'\\nonumber|\\notag', content))
        
        # If there's a significant mix, warn
        total = numbered + unnumbered
        if total > 3 and numbered > 0 and unnumbered > 0:
            ratio = min(numbered, unnumbered) / total
            if ratio > 0.2:  # More than 20% in minority
                results.append(self._create_result(
                    passed=False,
                    severity=CheckSeverity.INFO,
                    message=f"Mixed equation numbering: {numbered} numbered, {unnumbered} unnumbered",
                    suggestion="Consider consistent numbering strategy"
                ))
        
        return results
    
    def _check_inline_math_consistency(self, content: str) -> List[CheckResult]:
        """Check for mixed inline math delimiters."""
        results = []
        
        # Count different inline math styles
        dollar_count = len(re.findall(r'(?<!\$)\$(?!\$)[^$]+\$(?!\$)', content))
        paren_count = len(re.findall(r'\\\(.*?\\\)', content))
        
        if dollar_count > 0 and paren_count > 0:
            results.append(self._create_result(
                passed=False,
                severity=CheckSeverity.INFO,
                message=f"Mixed inline math: ${dollar_count} \\$...\\$ and {paren_count} \\(...\\)",
                suggestion="Use consistent inline math delimiters throughout"
            ))
        
        return results