@@ -17,22 +17,33 @@ class CompilerMixin:
1717 def compile_latex_with_errors (self ) -> tuple :
1818 """Compile LaTeX and return (success, error_string).
1919
20- On success: returns (True, "").
21- On failure : returns (False, structured_errors) where structured_errors
22- contains up to 5 error blocks parsed from main.log, with stderr fallback .
20+ On success with no errors : returns (True, "").
21+ On success with errors (PDF exists but has issues) : returns (True, error_string).
22+ On failure: returns (False, structured_errors) .
2323 """
2424 success = self .compile_latex ()
25- if success :
25+ compile_errors = getattr (self , '_last_compile_errors' , [])
26+
27+ if success and not compile_errors :
2628 return True , ""
2729
30+ if success and compile_errors :
31+ # PDF exists but has issues (undefined refs, bibtex errors, etc.)
32+ error_str = "PDF was generated but has the following issues:\n \n "
33+ error_str += "\n " .join (f"- { e [:150 ]} " for e in compile_errors [:10 ])
34+ return True , error_str
35+
36+ # PDF not generated — real failure
2837 log_path = self .latex_dir / "main.log"
2938 errors = self ._extract_latex_errors (log_path )
3039
31- # If log parsing found nothing, use captured stderr as fallback
3240 stderr = getattr (self , '_last_compile_stderr' , '' )
3341 if "no specific errors found" in errors and stderr :
3442 errors += f"\n \n Stderr from last pdflatex run:\n { stderr [:1000 ]} "
3543
44+ if compile_errors :
45+ errors += "\n \n Additional issues:\n " + "\n " .join (f"- { e [:150 ]} " for e in compile_errors [:10 ])
46+
3647 return False , errors
3748
3849 def _extract_latex_errors (self , log_path : Path ) -> str :
@@ -182,12 +193,15 @@ def _auto_fix_latex(self):
182193 def compile_latex (self ) -> bool :
183194 """Compile the LaTeX paper.
184195
196+ Uses nonstopmode to avoid interactive prompts, but parses log/blg
197+ files for actual errors (undefined citations, bibtex syntax errors, etc.).
198+
185199 On success, stores the PDF path in self._latest_pdf.
186- Returns True/False for backward compat; callers that need the
187- path should read self._latest_pdf.
200+ Returns True/False for backward compat.
188201 """
189202 self .log ("Compiling LaTeX..." )
190203 self ._last_compile_stderr = ""
204+ self ._last_compile_errors = []
191205 try :
192206 for cmd in [
193207 ["pdflatex" , "-interaction=nonstopmode" , "main.tex" ],
@@ -205,7 +219,26 @@ def compile_latex(self) -> bool:
205219 stdout = result .stdout .decode ("utf-8" , errors = "replace" ) if isinstance (result .stdout , bytes ) else (result .stdout or "" )
206220 if result .returncode != 0 and "main.tex" in cmd :
207221 self ._last_compile_stderr = stderr [:1000 ] or stdout [- 1000 :]
208- self .log (f"LaTeX compilation warning: { stderr [:500 ]} " )
222+
223+ # Parse log for real errors (not just warnings)
224+ log_path = self .latex_dir / "main.log"
225+ blg_path = self .latex_dir / "main.blg"
226+
227+ if log_path .exists ():
228+ log_text = log_path .read_text (errors = "replace" )
229+ # LaTeX errors start with "!"
230+ latex_errors = [l .strip () for l in log_text .split ("\n " ) if l .startswith ("!" )]
231+ if latex_errors :
232+ self ._last_compile_errors .extend (latex_errors [:10 ])
233+
234+ if blg_path .exists ():
235+ blg_text = blg_path .read_text (errors = "replace" )
236+ # BibTeX errors
237+ import re as _re
238+ bib_errors = _re .findall (r"^.*error message.*$|^I was expecting.*$|^Warning--I didn't find a database entry.*$" ,
239+ blg_text , _re .MULTILINE )
240+ if bib_errors :
241+ self ._last_compile_errors .extend (bib_errors [:10 ])
209242
210243 pdf_path = self .latex_dir / "main.pdf"
211244 if pdf_path .exists () and pdf_path .stat ().st_size > 0 :
@@ -215,10 +248,17 @@ def compile_latex(self) -> bool:
215248 self ._overfull_warnings = self ._parse_overfull_warnings ()
216249 page_info = f", { self ._body_page_count :.1f} body pages ({ self ._pdf_page_count } total)" if self ._body_page_count else ""
217250 overfull_info = f", { len (self ._overfull_warnings )} overfull warnings" if self ._overfull_warnings else ""
218- self .log (f"LaTeX compiled successfully: { pdf_path } ({ pdf_path .stat ().st_size } bytes{ page_info } { overfull_info } )" )
251+ error_info = f", { len (self ._last_compile_errors )} errors" if self ._last_compile_errors else ""
252+ self .log (f"LaTeX compiled successfully: { pdf_path } ({ pdf_path .stat ().st_size } bytes{ page_info } { overfull_info } { error_info } )" )
253+ if self ._last_compile_errors :
254+ for err in self ._last_compile_errors [:5 ]:
255+ self .log (f" Compile issue: { err [:120 ]} " , "WARN" )
219256 return True
220257 else :
221258 self .log ("LaTeX compilation failed: PDF not generated" )
259+ if self ._last_compile_errors :
260+ for err in self ._last_compile_errors [:5 ]:
261+ self .log (f" Error: { err [:120 ]} " , "ERROR" )
222262 return False
223263 except Exception as e :
224264 self .log (f"LaTeX compilation error: { e } " )
0 commit comments