diff --git a/Documents/compilerinterfaces.txt b/Documents/compilerinterfaces.txt index 17a3c971..80a41f1e 100644 --- a/Documents/compilerinterfaces.txt +++ b/Documents/compilerinterfaces.txt @@ -20,12 +20,11 @@ With this interface supports the following placeholders in command-line paramete %FO indicates the output path and filename. -%PI indicates the path of the input file (without filename). - -%PO indicates the path of the output file (without filename). - %PT indicates the temporary directory path where the compiler is located. +%PW indicates the path of the open wad file when compiled as internal script lump. +If compiled as file, or the wad file is not saved, %PW is the same as %PT + These placeholders are case-sensitive! ------------------------------------------------------------------------------------- diff --git a/Source/Compilers/AccCompiler.cs b/Source/Compilers/AccCompiler.cs index b6e05b91..76455c69 100644 --- a/Source/Compilers/AccCompiler.cs +++ b/Source/Compilers/AccCompiler.cs @@ -26,15 +26,19 @@ using System.Diagnostics; using System.IO; using CodeImp.DoomBuilder.Config; using CodeImp.DoomBuilder.IO; +using System.Windows.Forms; +using System.Text.RegularExpressions; #endregion namespace CodeImp.DoomBuilder.Compilers { - public sealed class AccCompiler : Compiler + internal sealed class AccCompiler : Compiler { #region ================== Constants + private const string ACS_ERROR_FILE = "acs.err"; + #endregion #region ================== Variables @@ -65,15 +69,113 @@ namespace CodeImp.DoomBuilder.Compilers #region ================== Methods - // This runs the compiler with a file as input. - public override bool CompileFile(string filename) - { - return true; - } - - // This runs the compiler with lump data as input. - public override bool CompileLump(Stream lumpdata) + // This runs the compiler + public override bool Run() { + ProcessStartInfo processinfo; + Process process; + TimeSpan deltatime; + string waddir = null; + int line = 0; + + // Find wad directory + if(General.Map.FilePathName.Length > 0) + waddir = Path.GetDirectoryName(General.Map.FilePathName); + + // When no luck, use temp path + if(waddir == null) waddir = this.tempdir.FullName; + + // Create parameters + string args = this.parameters; + args = args.Replace("%FI", inputfile); + args = args.Replace("%FO", outputfile); + args = args.Replace("%PT", this.tempdir.FullName); + args = args.Replace("%PW", waddir); + + // Setup process info + processinfo = new ProcessStartInfo(); + processinfo.Arguments = args; + processinfo.FileName = Path.Combine(this.tempdir.FullName, info.ProgramFile); + processinfo.CreateNoWindow = false; + processinfo.ErrorDialog = false; + processinfo.UseShellExecute = true; + processinfo.WindowStyle = ProcessWindowStyle.Hidden; + processinfo.WorkingDirectory = this.workingdir; + + // Output info + General.WriteLogLine("Running compiler..."); + General.WriteLogLine("Program: " + processinfo.FileName); + General.WriteLogLine("Arguments: " + processinfo.Arguments); + + try + { + // Start the compiler + process = Process.Start(processinfo); + } + catch(Exception e) + { + // Unable to start the compiler + General.ShowErrorMessage("Unable to start the compiler (" + info.Name + "). " + e.GetType().Name + ": " + e.Message, MessageBoxButtons.OK); + return false; + } + + // Wait for compiler to complete + process.WaitForExit(); + 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.tempdir.FullName, ACS_ERROR_FILE); + if(File.Exists(errfile)) + { + try + { + // Regex to find error lines + Regex errlinematcher = new Regex("\\:[0-9]+\\:\\b", RegexOptions.Compiled | RegexOptions.CultureInvariant); + + // Read all lines + string[] errlines = File.ReadAllLines(errfile); + 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; + + // Everything before the match is the filename + err.filename = linestr.Substring(0, match.Index); + if(!Path.IsPathRooted(err.filename)) + { + // Add working directory to filename + 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); + } + + // Next line + line++; + } + } + catch(Exception e) + { + // Error reading errors (ironic, isn't it) + ReportError(new CompilerError("Failed to retrieve compiler error report. " + e.GetType().Name + ": " + e.Message)); + } + } + return true; } diff --git a/Source/Compilers/Compiler.cs b/Source/Compilers/Compiler.cs index ddc07217..289e3bb1 100644 --- a/Source/Compilers/Compiler.cs +++ b/Source/Compilers/Compiler.cs @@ -40,6 +40,8 @@ namespace CodeImp.DoomBuilder.Compilers protected CompilerInfo info; protected string parameters; protected string workingdir; + protected string outputfile; + protected string inputfile; // Files protected DirectoryInfo tempdir; @@ -53,9 +55,11 @@ namespace CodeImp.DoomBuilder.Compilers #endregion #region ================== Properties - + public string Parameters { get { return parameters; } set { parameters = value; } } public string WorkingDirectory { get { return workingdir; } set { workingdir = value; } } + public string InputFile { get { return inputfile; } set { inputfile = value; } } + public string OutputFile { get { return outputfile; } set { outputfile = value; } } public string Location { get { return tempdir.FullName; } } public bool IsDisposed { get { return isdisposed; } } public CompilerError[] Errors { get { return errors.ToArray(); } } @@ -114,16 +118,10 @@ namespace CodeImp.DoomBuilder.Compilers } /// - /// This runs the compiler with a file as input. + /// This runs the compiler. /// /// Returns false when failed to start. - public virtual bool CompileFile(string filename) { return false; } - - /// - /// This runs the compiler with lump data as input. - /// - /// Returns false when failed to start. - public virtual bool CompileLump(Stream lumpdata) { return false; } + public virtual bool Run() { return false; } /// /// Use this to report an error. @@ -151,8 +149,14 @@ namespace CodeImp.DoomBuilder.Compilers // Go for all assemblies foreach(Assembly a in asms) { + Type[] types; + // Find the class - Type[] types = a.GetExportedTypes(); + if(a == General.ThisAssembly) + types = a.GetTypes(); + else + types = a.GetExportedTypes(); + foreach(Type t in types) { if(t.IsSubclassOf(typeof(Compiler)) && (t.Name == info.ProgramInterface)) diff --git a/Source/Compilers/CompilerError.cs b/Source/Compilers/CompilerError.cs index 3a47a08e..956317d9 100644 --- a/Source/Compilers/CompilerError.cs +++ b/Source/Compilers/CompilerError.cs @@ -30,11 +30,30 @@ namespace CodeImp.DoomBuilder.Compilers { public struct CompilerError { + // Constants + public const int NO_LINE_NUMBER = -1; + // Members public string description; public string filename; public int linenumber; + // Constructor + public CompilerError(string description) + { + this.description = description; + this.filename = ""; + this.linenumber = NO_LINE_NUMBER; + } + + // Constructor + public CompilerError(string description, string filename) + { + this.description = description; + this.filename = filename; + this.linenumber = NO_LINE_NUMBER; + } + // Constructor public CompilerError(string description, string filename, int linenumber) { diff --git a/Source/Compilers/NodesCompiler.cs b/Source/Compilers/NodesCompiler.cs index 4de0c486..6ab73156 100644 --- a/Source/Compilers/NodesCompiler.cs +++ b/Source/Compilers/NodesCompiler.cs @@ -31,7 +31,7 @@ using System.Windows.Forms; namespace CodeImp.DoomBuilder.Compilers { - public sealed class NodesCompiler : Compiler + internal sealed class NodesCompiler : Compiler { #region ================== Constants @@ -39,15 +39,10 @@ namespace CodeImp.DoomBuilder.Compilers #region ================== Variables - // Output file - private string outputfile; - #endregion #region ================== Properties - public string OutputFile { get { return outputfile; } set { outputfile = value; } } - #endregion #region ================== Constructor / Disposer @@ -79,7 +74,7 @@ namespace CodeImp.DoomBuilder.Compilers #region ================== Methods // This runs the compiler with a file as input. - public override bool CompileFile(string filename) + public override bool Run() { ProcessStartInfo processinfo; Process process; @@ -87,7 +82,7 @@ namespace CodeImp.DoomBuilder.Compilers // Create parameters string args = this.parameters; - args = args.Replace("%FI", filename); + args = args.Replace("%FI", inputfile); args = args.Replace("%FO", outputfile); // Setup process info @@ -98,7 +93,7 @@ namespace CodeImp.DoomBuilder.Compilers processinfo.ErrorDialog = false; processinfo.UseShellExecute = true; processinfo.WindowStyle = ProcessWindowStyle.Hidden; - processinfo.WorkingDirectory = this.tempdir.FullName; + processinfo.WorkingDirectory = this.workingdir; // Output info General.WriteLogLine("Running compiler..."); diff --git a/Source/Config/NodebuilderInfo.cs b/Source/Config/NodebuilderInfo.cs index a7eb15e5..79af5b93 100644 --- a/Source/Config/NodebuilderInfo.cs +++ b/Source/Config/NodebuilderInfo.cs @@ -66,7 +66,7 @@ namespace CodeImp.DoomBuilder.Config string compilername; General.WriteLogLine("Registered nodebuilder configuration '" + name + "' from '" + filename + "'"); - + // Initialize this.name = name; this.compiler = null; @@ -111,20 +111,9 @@ namespace CodeImp.DoomBuilder.Config } // This runs the nodebuilder - public NodesCompiler CreateCompiler() + public Compiler CreateCompiler() { - Compiler c = compiler.Create(); - if(c is NodesCompiler) - { - NodesCompiler ns = (c as NodesCompiler); - ns.Parameters = parameters; - return ns; - } - else - { - // Nodebuilders must use a NodesCompiler! - throw new ArgumentException("Cannot create compiler interface '" + compiler.ProgramInterface + "' for nodebuilder '" + name + "'. Nodebuilders must use a NodesCompiler compiler interface!"); - } + return compiler.Create(); } #endregion diff --git a/Source/Controls/ScriptDocumentTab.cs b/Source/Controls/ScriptDocumentTab.cs index 4e10ef2c..4b4104a1 100644 --- a/Source/Controls/ScriptDocumentTab.cs +++ b/Source/Controls/ScriptDocumentTab.cs @@ -93,7 +93,12 @@ namespace CodeImp.DoomBuilder.Controls #endregion #region ================== Methods - + + // This compiles the script + public virtual void Compile() + { + } + // This saves the document (used for both explicit and implicit) // Return true when successfully saved public virtual bool Save() diff --git a/Source/Controls/ScriptEditorPanel.cs b/Source/Controls/ScriptEditorPanel.cs index 27fa74c9..d9a9fc1c 100644 --- a/Source/Controls/ScriptEditorPanel.cs +++ b/Source/Controls/ScriptEditorPanel.cs @@ -394,7 +394,7 @@ namespace CodeImp.DoomBuilder.Controls return true; } } - + // A tab is selected private void tabs_Selecting(object sender, TabControlCancelEventArgs e) { @@ -408,16 +408,19 @@ namespace CodeImp.DoomBuilder.Controls CloseScript(t, false); UpdateToolbar(); } - + // Compile Script clicked private void buttoncompile_Click(object sender, EventArgs e) { // First save all implicit scripts to the temporary wad file ImplicitSave(); - // TODO: Now compile this lump + // Compile script + ScriptDocumentTab t = (tabs.SelectedTab as ScriptDocumentTab); + t.Compile(); + UpdateToolbar(); } - + // Undo clicked private void buttonundo_Click(object sender, EventArgs e) { @@ -425,7 +428,7 @@ namespace CodeImp.DoomBuilder.Controls t.Undo(); UpdateToolbar(); } - + // Redo clicked private void buttonredo_Click(object sender, EventArgs e) { @@ -433,7 +436,7 @@ namespace CodeImp.DoomBuilder.Controls t.Redo(); UpdateToolbar(); } - + // Cut clicked private void buttoncut_Click(object sender, EventArgs e) { @@ -441,7 +444,7 @@ namespace CodeImp.DoomBuilder.Controls t.Cut(); UpdateToolbar(); } - + // Copy clicked private void buttoncopy_Click(object sender, EventArgs e) { diff --git a/Source/Controls/ScriptFileDocumentTab.cs b/Source/Controls/ScriptFileDocumentTab.cs index a975af4d..9daa6a5e 100644 --- a/Source/Controls/ScriptFileDocumentTab.cs +++ b/Source/Controls/ScriptFileDocumentTab.cs @@ -30,6 +30,7 @@ using CodeImp.DoomBuilder.Config; using CodeImp.DoomBuilder.Types; using CodeImp.DoomBuilder.IO; using System.IO; +using CodeImp.DoomBuilder.Compilers; #endregion @@ -80,6 +81,49 @@ namespace CodeImp.DoomBuilder.Controls #region ================== Methods + // This compiles the script file + public override void Compile() + { + DirectoryInfo tempdir; + Compiler compiler; + string inputfile, outputfile; + + // List of errors + List errors = new List(); + + try + { + // Initialize compiler + compiler = config.Compiler.Create(); + } + catch(Exception e) + { + // Fail + errors.Add(new CompilerError("Unable to initialize compiler. " + e.GetType().Name + ": " + e.Message)); + return; + } + + // Make random output filename + outputfile = General.MakeTempFilename(compiler.Location, "tmp"); + + // Run compiler + compiler.Parameters = config.Parameters; + compiler.InputFile = Path.GetFileName(filepathname); + compiler.OutputFile = Path.GetFileName(outputfile); + compiler.WorkingDirectory = Path.GetDirectoryName(filepathname); + if(compiler.Run()) + { + // Fetch errors + errors.AddRange(compiler.Errors); + } + + // Dispose compiler + compiler.Dispose(); + + // TODO: Feed errors to panel + + } + // This saves the document (used for both explicit and implicit) // Return true when successfully saved public override bool Save() diff --git a/Source/Controls/ScriptLumpDocumentTab.cs b/Source/Controls/ScriptLumpDocumentTab.cs index eb108e3d..0dc0723a 100644 --- a/Source/Controls/ScriptLumpDocumentTab.cs +++ b/Source/Controls/ScriptLumpDocumentTab.cs @@ -40,9 +40,9 @@ namespace CodeImp.DoomBuilder.Controls #region ================== Constants #endregion - + #region ================== Variables - + private string lumpname; #endregion @@ -90,6 +90,13 @@ namespace CodeImp.DoomBuilder.Controls #region ================== Methods + // Compile script + public override void Compile() + { + // Do it! + General.Map.CompileLump(lumpname); + } + // Implicit save public override bool Save() { diff --git a/Source/General/MapManager.cs b/Source/General/MapManager.cs index 18ec0634..138483a5 100644 --- a/Source/General/MapManager.cs +++ b/Source/General/MapManager.cs @@ -89,6 +89,7 @@ namespace CodeImp.DoomBuilder private Launcher launcher; private ThingsFilter thingsfilter; private ScriptEditorForm scriptwindow; + private List errors; // Disposing private bool isdisposed = false; @@ -121,6 +122,7 @@ namespace CodeImp.DoomBuilder public IMapSetIO FormatInterface { get { return io; } } internal Launcher Launcher { get { return launcher; } } public ThingsFilter ThingsFilter { get { return thingsfilter; } } + internal List Errors { get { return errors; } } public bool IsScriptsWindowOpen { get { return (scriptwindow != null) && !scriptwindow.IsDisposed; } } #endregion @@ -557,7 +559,7 @@ namespace CodeImp.DoomBuilder { // Create the compiler interface that will run the nodebuilder // This automatically creates a temporary directory for us - NodesCompiler compiler = nodebuilder.CreateCompiler(); + Compiler compiler = nodebuilder.CreateCompiler(); // Make temporary filename tempfile1 = General.MakeTempFilename(compiler.Location); @@ -587,8 +589,11 @@ namespace CodeImp.DoomBuilder buildwad.Dispose(); // Run the nodebuilder + compiler.Parameters = nodebuilder.Parameters; + compiler.InputFile = Path.GetFileName(tempfile1); compiler.OutputFile = Path.GetFileName(tempfile2); - if(compiler.CompileFile(Path.GetFileName(tempfile1))) + compiler.WorkingDirectory = Path.GetDirectoryName(tempfile1); + if(compiler.Run()) { // Open the output file buildwad = new WAD(tempfile2); @@ -1116,27 +1121,84 @@ namespace CodeImp.DoomBuilder } // This compiles a script lump and returns any errors that may have occurred - internal CompilerError[] CompileLump(Lump scriptlump, bool showwarning) + // Returns true when our code worked properly (even when the compiler returned errors) + internal bool CompileLump(string lumpname) { DirectoryInfo tempdir; - CompilerError[] errors; + Compiler compiler; + string inputfile, outputfile; + + // Find the lump + Lump lump = tempwad.FindLump(lumpname); + if(lump == null) throw new Exception("No such lump in temporary wad file '" + lumpname + "'."); + + // New list of errors + errors = new List(); // Determine the script configuration to use - ScriptConfiguration scriptconfig = config.MapLumps[scriptlump.Name].script; - - // Initialize compiler - Compiler compiler = scriptconfig.Compiler.Create(); - + ScriptConfiguration scriptconfig = config.MapLumps[lump.Name].script; + + try + { + // Initialize compiler + compiler = scriptconfig.Compiler.Create(); + } + catch(Exception e) + { + // Fail + errors.Add(new CompilerError("Unable to initialize compiler. " + e.GetType().Name + ": " + e.Message)); + return false; + } + + try + { + // Write lump data to temp script file in compiler's temp directory + inputfile = General.MakeTempFilename(compiler.Location, "tmp"); + lump.Stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(lump.Stream); + File.WriteAllBytes(inputfile, reader.ReadBytes((int)lump.Stream.Length)); + } + catch(Exception e) + { + // Fail + compiler.Dispose(); + errors.Add(new CompilerError("Unable to write script to working file. " + e.GetType().Name + ": " + e.Message)); + return false; + } + + // Make random output filename + outputfile = General.MakeTempFilename(compiler.Location, "tmp"); + // Run compiler compiler.Parameters = scriptconfig.Parameters; - - errors = compiler.Errors; - - // Clean up - compiler.Dispose(); - - // Done - return errors; + compiler.InputFile = Path.GetFileName(inputfile); + compiler.OutputFile = Path.GetFileName(outputfile); + compiler.WorkingDirectory = Path.GetDirectoryName(inputfile); + if(compiler.Run()) + { + // Fetch errors + errors.AddRange(compiler.Errors); + + // Clean up + compiler.Dispose(); + if(errors.Count == 0) errors = null; + + // Done + return true; + } + else + { + // Fail + compiler.Dispose(); + errors = null; + return false; + } + } + + // This clears all compiler errors + internal void ClearCompilerErrors() + { + errors = null; } #endregion