From 4e34897ac1f8e89dbf95e9c012555e020af46dd2 Mon Sep 17 00:00:00 2001
From: ZZYZX <zzyzx@virtual>
Date: Sun, 28 Jan 2024 02:16:42 +0200
Subject: [PATCH] Fixed BCS compiler error reporting

---
 Build/Compilers/BCC/bcc.cfg          |   2 +-
 Build/Updater.ini                    |   6 +-
 Source/Core/Builder.csproj           |   1 +
 Source/Core/Compilers/AccCompiler.cs | 189 +++++++++++++++------------
 Source/Core/Compilers/BccCompiler.cs |  83 ++++++++++++
 5 files changed, 192 insertions(+), 89 deletions(-)
 create mode 100644 Source/Core/Compilers/BccCompiler.cs

diff --git a/Build/Compilers/BCC/bcc.cfg b/Build/Compilers/BCC/bcc.cfg
index ccac46828..5d1d733bb 100755
--- a/Build/Compilers/BCC/bcc.cfg
+++ b/Build/Compilers/BCC/bcc.cfg
@@ -7,7 +7,7 @@ compilers
 	// All others are the required files (the setting names do not matter)
 	bcc
 	{
-		interface = "AccCompiler";
+		interface = "BccCompiler";
 		program = "bcc.exe";
 		zcommon = "zcommon.bcs";
 		std = "std.acs";
diff --git a/Build/Updater.ini b/Build/Updater.ini
index 29e7b98a2..734436a2b 100755
--- a/Build/Updater.ini
+++ b/Build/Updater.ini
@@ -1,5 +1,5 @@
 URL https://ultimatedoombuilder.github.io/files/
 FileName Builder.exe
-UpdateName UltimateDoomBuilder-r[REVNUM]-x86.7z
-InstallerName UltimateDoomBuilder-Setup-R[REVNUM]-x86.exe
-UpdaterName UDB_Updater-x86.7z
\ No newline at end of file
+UpdateName UltimateDoomBuilder-r[REVNUM]-x64.7z
+InstallerName UltimateDoomBuilder-Setup-R[REVNUM]-x64.exe
+UpdaterName UDB_Updater-x64.7z
\ No newline at end of file
diff --git a/Source/Core/Builder.csproj b/Source/Core/Builder.csproj
index 54cd51117..37576b03e 100644
--- a/Source/Core/Builder.csproj
+++ b/Source/Core/Builder.csproj
@@ -141,6 +141,7 @@
   -->
   <ItemGroup>
     <Compile Include="Compilers\AccCompiler.cs" />
+    <Compile Include="Compilers\BccCompiler.cs" />
     <Compile Include="Compilers\NodesCompiler.cs" />
     <Compile Include="Config\ArgumentInfo.cs" />
     <Compile Include="Config\ExternalCommandSettings.cs" />
diff --git a/Source/Core/Compilers/AccCompiler.cs b/Source/Core/Compilers/AccCompiler.cs
index 867d1a471..ac5b1b448 100755
--- a/Source/Core/Compilers/AccCompiler.cs
+++ b/Source/Core/Compilers/AccCompiler.cs
@@ -30,11 +30,17 @@ using CodeImp.DoomBuilder.ZDoom.Scripting;
 
 namespace CodeImp.DoomBuilder.Compilers
 {
-	internal sealed class AccCompiler : Compiler
+	internal class AccCompiler : Compiler
 	{
-		#region ================== Constants
-		
-		private const string ACS_ERROR_FILE = "acs.err";
+		#region ================== Internal classes
+
+		protected class CompileContext { }
+
+        #endregion
+
+        #region ================== Constants
+
+        private const string ACS_ERROR_FILE = "acs.err";
 		
 		#endregion
 		
@@ -74,12 +80,102 @@ namespace CodeImp.DoomBuilder.Compilers
 		#endregion
 		
 		#region ================== Methods
+
+		protected virtual CompileContext OnBeforeProcessStart(ProcessStartInfo info)
+        {
+			return new CompileContext();
+		}
+
+		protected virtual void OnCheckError(HashSet<string> includes, ProcessStartInfo processinfo, Process process, CompileContext context)
+        {
+			int line = 0;
+
+			// Now find the error file
+			string errfile = Path.Combine(this.workingdir, ACS_ERROR_FILE);
+			if (File.Exists(errfile))
+			{
+				try
+				{
+					// Regex to find error lines
+					Regex errlinematcher = new Regex(":[0-9]+: ", RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+					// Read all lines
+					bool erroradded = false; //mxd
+					string[] errlines = File.ReadAllLines(errfile);
+					string temppath = this.tempdir.FullName + Path.DirectorySeparatorChar.ToString(); //mxd. Need trailing slash..
+					while (line < errlines.Length)
+					{
+						// Check line
+						string linestr = errlines[line];
+						Match match = errlinematcher.Match(linestr);
+						if (match.Success && (match.Index > 0))
+						{
+							CompilerError err = new CompilerError();
+
+							// The match without spaces and semicolon is the line number
+							string linenr = match.Value.Replace(":", "").Trim();
+							if (!int.TryParse(linenr, out err.linenumber))
+								err.linenumber = CompilerError.NO_LINE_NUMBER;
+							else
+								err.linenumber--;
+
+							// Everything before the match is the filename
+							err.filename = linestr.Substring(0, match.Index).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+
+							//mxd. Get rid of temp directory path
+							if (err.filename.StartsWith(temppath)) err.filename = err.filename.Replace(temppath, string.Empty);
+
+							if (!Path.IsPathRooted(err.filename))
+							{
+								//mxd. If the error is in an include file, try to find it in loaded resources
+								if (includes.Contains(err.filename))
+								{
+									foreach (DataReader dr in General.Map.Data.Containers)
+									{
+										if (dr is DirectoryReader && dr.FileExists(err.filename))
+										{
+											err.filename = Path.Combine(dr.Location.location, err.filename);
+											break;
+										}
+									}
+								}
+								else
+								{
+									// Add working directory to filename, so it could be recognized as map namespace lump in MapManager.CompileLump()
+									err.filename = Path.Combine(processinfo.WorkingDirectory, err.filename);
+								}
+							}
+
+							// Everything after the match is the description
+							err.description = linestr.Substring(match.Index + match.Length).Trim();
+
+							// Report the error
+							ReportError(err);
+							erroradded = true; //mxd
+						}
+
+						// Next line
+						line++;
+					}
+
+					//mxd. Some ACC errors are not properly formatted. If that's the case, threat the whole acs.err as an error...
+					if (!erroradded && errlines.Length > 0)
+					{
+						ReportError(new CompilerError(string.Join(Environment.NewLine, errlines)));
+					}
+				}
+				catch (Exception e)
+				{
+					// Error reading errors (ironic, isn't it)
+					ReportError(new CompilerError("Failed to retrieve compiler error report. " + e.GetType().Name + ": " + e.Message));
+				}
+			}
+		}
 		
 		// This runs the compiler
 		public override bool Run()
 		{
 			Process process;
-			int line = 0;
 			string sourcedir = Path.GetDirectoryName(sourcefile);
 
 			// Preprocess the file
@@ -174,6 +270,8 @@ namespace CodeImp.DoomBuilder.Compilers
 			processinfo.UseShellExecute = true;
 			processinfo.WindowStyle = ProcessWindowStyle.Hidden;
 			processinfo.WorkingDirectory = this.workingdir;
+
+			CompileContext context = OnBeforeProcessStart(processinfo);
 			
 			// Output info
 			General.WriteLogLine("Running compiler...");
@@ -197,87 +295,8 @@ namespace CodeImp.DoomBuilder.Compilers
 			TimeSpan deltatime = TimeSpan.FromTicks(process.ExitTime.Ticks - process.StartTime.Ticks);
 			General.WriteLogLine("Compiler process has finished.");
 			General.WriteLogLine("Compile time: " + deltatime.TotalSeconds.ToString("########0.00") + " seconds");
-			
-			// Now find the error file
-			string errfile = Path.Combine(this.workingdir, ACS_ERROR_FILE);
-			if(File.Exists(errfile))
-			{
-				try
-				{
-					// Regex to find error lines
-					Regex errlinematcher = new Regex(":[0-9]+: ", RegexOptions.Compiled | RegexOptions.CultureInvariant);
-					
-					// Read all lines
-					bool erroradded = false; //mxd
-					string[] errlines = File.ReadAllLines(errfile);
-					string temppath = this.tempdir.FullName + Path.DirectorySeparatorChar.ToString(); //mxd. Need trailing slash..
-					while(line < errlines.Length)
-					{
-						// Check line
-						string linestr = errlines[line];
-						Match match = errlinematcher.Match(linestr);
-						if(match.Success && (match.Index > 0))
-						{
-							CompilerError err = new CompilerError();
-							
-							// The match without spaces and semicolon is the line number
-							string linenr = match.Value.Replace(":", "").Trim();
-							if(!int.TryParse(linenr, out err.linenumber))
-								err.linenumber = CompilerError.NO_LINE_NUMBER;
-							else
-								err.linenumber--;
-							
-							// Everything before the match is the filename
-							err.filename = linestr.Substring(0, match.Index).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
-
-							//mxd. Get rid of temp directory path
-							if(err.filename.StartsWith(temppath)) err.filename = err.filename.Replace(temppath, string.Empty);
-							
-							if(!Path.IsPathRooted(err.filename))
-							{
-								//mxd. If the error is in an include file, try to find it in loaded resources
-								if(includes.Contains(err.filename))
-								{
-									foreach(DataReader dr in General.Map.Data.Containers)
-									{
-										if(dr is DirectoryReader && dr.FileExists(err.filename))
-										{
-											err.filename = Path.Combine(dr.Location.location, err.filename);
-											break;
-										}
-									}
-								}
-								else
-								{
-									// Add working directory to filename, so it could be recognized as map namespace lump in MapManager.CompileLump()
-									err.filename = Path.Combine(processinfo.WorkingDirectory, err.filename);
-								}
-							}
-							
-							// Everything after the match is the description
-							err.description = linestr.Substring(match.Index + match.Length).Trim();
-							
-							// Report the error
-							ReportError(err);
-							erroradded = true; //mxd
-						}
-						
-						// Next line
-						line++;
-					}
 
-					//mxd. Some ACC errors are not properly formatted. If that's the case, threat the whole acs.err as an error...
-					if(!erroradded && errlines.Length > 0)
-					{
-						ReportError(new CompilerError(string.Join(Environment.NewLine, errlines)));
-					}
-				}
-				catch(Exception e)
-				{
-					// Error reading errors (ironic, isn't it)
-					ReportError(new CompilerError("Failed to retrieve compiler error report. " + e.GetType().Name + ": " + e.Message));
-				}
-			}
+			OnCheckError(includes, processinfo, process, context);
 			
 			return true;
 		}
diff --git a/Source/Core/Compilers/BccCompiler.cs b/Source/Core/Compilers/BccCompiler.cs
new file mode 100644
index 000000000..753107008
--- /dev/null
+++ b/Source/Core/Compilers/BccCompiler.cs
@@ -0,0 +1,83 @@
+using CodeImp.DoomBuilder.Config;
+using CodeImp.DoomBuilder.Data;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+
+namespace CodeImp.DoomBuilder.Compilers
+{
+    internal class BccCompiler : AccCompiler
+    {
+        public BccCompiler(CompilerInfo info) : base(info) {}
+
+        protected override CompileContext OnBeforeProcessStart(ProcessStartInfo info)
+        {
+            info.UseShellExecute = false;
+            info.CreateNoWindow = true;
+            info.RedirectStandardError = true;
+            info.RedirectStandardOutput = true;
+            return new CompileContext();
+        }
+
+        protected override void OnCheckError(HashSet<string> includes, ProcessStartInfo processinfo, Process process, CompileContext context)
+        {
+            if (process.ExitCode != 0)
+            {
+                bool foundAnyErrors = false;
+                string[] errorLines = process.StandardOutput.ReadToEnd().Split('\n');
+
+                foreach (string rawErrorLine in errorLines)
+                {
+                    string[] rawError = rawErrorLine.Split(new char[] { ':' }, 4);
+                    if (rawError.Length != 4)
+                        continue;
+                    string errorFile = rawError[0];
+                    int errorLine;
+                    if (!int.TryParse(rawError[1], out errorLine))
+                        continue;
+                    errorLine--;
+                    // rawError[2] is ignored. in BCC, this contains the column at which the error happened. not supported in error viewer.
+                    string errorContent = rawError[3].Trim();
+
+                    // logic copied from AccCompiler
+                    string temppath = this.tempdir.FullName + Path.DirectorySeparatorChar.ToString(); //mxd. Need trailing slash..
+                    if (errorFile.StartsWith(temppath)) errorFile = errorFile.Replace(temppath, string.Empty);
+
+                    if (!Path.IsPathRooted(errorFile))
+                    {
+                        //mxd. If the error is in an include file, try to find it in loaded resources
+                        if (includes.Contains(errorFile))
+                        {
+                            foreach (DataReader dr in General.Map.Data.Containers)
+                            {
+                                if (dr is DirectoryReader && dr.FileExists(errorFile))
+                                {
+                                    errorFile = Path.Combine(dr.Location.location, errorFile);
+                                    break;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            // Add working directory to filename, so it could be recognized as map namespace lump in MapManager.CompileLump()
+                            errorFile = Path.Combine(processinfo.WorkingDirectory, errorFile);
+                        }
+                    }
+                    // end logic copied from AccCompiler
+
+                    CompilerError err = new CompilerError();
+                    err.linenumber = errorLine;
+                    err.filename = errorFile;
+                    err.description = errorContent;
+                    
+                    ReportError(err);
+                    foundAnyErrors = true;
+                }
+
+                if (!foundAnyErrors)
+                    ReportError(new CompilerError(string.Join(Environment.NewLine, errorLines)));
+            }
+        }
+    }
+}
-- 
GitLab