From cf837f3834790b38a2b074f3c18be27eb4952b2c Mon Sep 17 00:00:00 2001 From: wixoaGit Date: Wed, 27 Sep 2023 15:46:02 -0400 Subject: [PATCH] Add support for `#define FILE_DIR` --- .../Compiler/DMPreprocessor/DMPreprocessor.cs | 28 ++++++- DMCompiler/DM/Expressions/Constant.cs | 26 ++++--- DMCompiler/DMCompiler.cs | 75 +++++++++---------- 3 files changed, 79 insertions(+), 50 deletions(-) diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs index 49398833ba..59b7a3aa0d 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs @@ -299,7 +299,30 @@ private void HandleDefineDirective(Token defineToken) { GetLineOfTokens(); // consume what's on this line and leave return; } - if(defineIdentifier.Text == "defined") { + + // #define FILE_DIR is a little special + // Every define will add to a list of directories to check for resource files + if (defineIdentifier.Text == "FILE_DIR") { + Token dirToken = GetNextToken(true); + string? dirTokenValue = dirToken.Type switch { + TokenType.DM_Preproc_ConstantString => (string?)dirToken.Value, + TokenType.DM_Preproc_Punctuator_Period => ".", + _ =>null + }; + + if (dirTokenValue is null) { + DMCompiler.Emit(WarningCode.BadDirective, dirToken.Location, $"\"{dirToken.Text}\" is not a valid directory"); + return; + } + + DMPreprocessorLexer currentLexer = _lexerStack.Peek(); + string dir = Path.Combine(currentLexer.IncludeDirectory, dirTokenValue); + DMCompiler.AddResourceDirectory(dir); + + // In BYOND it goes on to set the FILE_DIR macro's value to the added directory + // I don't see any reason to do that + return; + } else if (defineIdentifier.Text == "defined") { DMCompiler.Emit(WarningCode.SoftReservedKeyword, defineIdentifier.Location, "Reserved keyword 'defined' cannot be used as macro name"); } @@ -310,11 +333,10 @@ private void HandleDefineDirective(Token defineToken) { if (macroToken.Type == TokenType.DM_Preproc_Punctuator_LeftParenthesis) { // We're a macro function! parameters = new List(1); //Read in the parameters - Token parameterToken; bool canConsumeComma = false; bool foundVariadic = false; while(true) { - parameterToken = GetNextToken(true); + var parameterToken = GetNextToken(true); switch(parameterToken.Type) { case TokenType.DM_Preproc_Identifier: canConsumeComma = true; diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index 7fa01a60f3..1894e2145f 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -364,24 +364,32 @@ public Resource(Location location, string filePath) : base(location) { // Treat backslashes as forward slashes on Linux filePath = filePath.Replace('\\', '/'); - string? finalFilePath = null; - - var outputDir = System.IO.Path.GetDirectoryName(DMCompiler.Settings.Files[0]) ?? "/"; + var outputDir = System.IO.Path.GetDirectoryName(DMCompiler.Settings.Files?[0]) ?? "/"; if (string.IsNullOrEmpty(outputDir)) outputDir = "./"; + string? finalFilePath = null; + var fileName = System.IO.Path.GetFileName(filePath); var fileDir = System.IO.Path.GetDirectoryName(filePath) ?? string.Empty; - var directory = FindDirectory(outputDir, fileDir); - if (directory != null) { - // Perform a case-insensitive search for the file - finalFilePath = FindFile(directory, fileName); + + // Search every defined FILE_DIR + foreach (string resourceDir in DMCompiler.ResourceDirectories) { + var directory = FindDirectory(resourceDir, fileDir); + + if (directory != null) { + // Perform a case-insensitive search for the file + finalFilePath = FindFile(directory, fileName); + + if (finalFilePath != null) + break; + } } - // Search relative to the source file if it wasn't in the project's directory + // Search relative to the source file if it wasn't in one of the FILE_DIRs if (finalFilePath == null) { var sourceDir = System.IO.Path.Combine(outputDir, System.IO.Path.GetDirectoryName(Location.SourceFile) ?? string.Empty); - directory = FindDirectory(sourceDir, fileDir); + var directory = FindDirectory(sourceDir, fileDir); if (directory != null) finalFilePath = FindFile(directory, fileName); diff --git a/DMCompiler/DMCompiler.cs b/DMCompiler/DMCompiler.cs index 69844b001e..8f7ba89f4d 100644 --- a/DMCompiler/DMCompiler.cs +++ b/DMCompiler/DMCompiler.cs @@ -20,11 +20,13 @@ namespace DMCompiler { //TODO: Make this not a static class public static class DMCompiler { - public static int ErrorCount = 0; - public static int WarningCount = 0; + public static int ErrorCount; + public static int WarningCount; public static DMCompilerSettings Settings; + public static IReadOnlyList ResourceDirectories => _resourceDirectories; - private static DMCompilerConfiguration Config; + private static readonly DMCompilerConfiguration Config = new(); + private static readonly List _resourceDirectories = new(); private static DateTime _compileStartTime; public static bool Compile(DMCompilerSettings settings) { @@ -32,7 +34,8 @@ public static bool Compile(DMCompilerSettings settings) { WarningCount = 0; Settings = settings; if (Settings.Files == null) return false; - Config = new(); + Config.Reset(); + _resourceDirectories.Clear(); //TODO: Only use InvariantCulture where necessary instead of it being the default CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; @@ -50,25 +53,18 @@ public static bool Compile(DMCompilerSettings settings) { DMPreprocessor preprocessor = Preprocess(settings.Files, settings.MacroDefines); bool successfulCompile = preprocessor is not null && Compile(preprocessor); - if (successfulCompile) - { + if (successfulCompile) { //Output file is the first file with the extension changed to .json string outputFile = Path.ChangeExtension(settings.Files[0], "json"); List maps = ConvertMaps(preprocessor.IncludedMaps); - if (ErrorCount > 0) - { + if (ErrorCount > 0) { successfulCompile = false; - } - else - { + } else { var output = SaveJson(maps, preprocessor.IncludedInterface, outputFile); - if (ErrorCount > 0) - { + if (ErrorCount > 0) { successfulCompile = false; - } - else - { + } else { Console.WriteLine($"Compilation succeeded with {WarningCount} warnings"); Console.WriteLine(output); } @@ -80,13 +76,19 @@ public static bool Compile(DMCompilerSettings settings) { } TimeSpan duration = DateTime.Now - _compileStartTime; - Console.WriteLine($"Total time: {duration.ToString(@"mm\:ss")}"); + Console.WriteLine($"Total time: {duration:mm\\:ss}"); return successfulCompile; } - private static DMPreprocessor? Preprocess(List files, Dictionary macroDefines) { - DMPreprocessor? build() { + public static void AddResourceDirectory(string dir) { + dir = dir.Replace('\\', Path.DirectorySeparatorChar); + + _resourceDirectories.Add(dir); + } + + private static DMPreprocessor? Preprocess(List files, Dictionary? macroDefines) { + DMPreprocessor? Build() { DMPreprocessor preproc = new DMPreprocessor(true); if (macroDefines != null) { foreach (var (key, value) in macroDefines) { @@ -130,7 +132,7 @@ public static bool Compile(DMCompilerSettings settings) { if (Settings.DumpPreprocessor) { //Preprocessing is done twice because the output is used up when dumping it StringBuilder result = new(); - foreach (Token t in build()) { + foreach (Token t in Build()) { result.Append(t.Text); } @@ -140,7 +142,8 @@ public static bool Compile(DMCompilerSettings settings) { File.WriteAllText(outputPath, result.ToString()); Console.WriteLine($"Preprocessor output dumped to {outputPath}"); } - return build(); + + return Build(); } private static bool Compile(IEnumerable preprocessedTokens) { @@ -154,11 +157,6 @@ private static bool Compile(IEnumerable preprocessedTokens) { Emit(warning); } - if (astFile is null) { - VerbosePrint("Parsing failed, exiting compilation"); - return false; - } - DMASTSimplifier astSimplifier = new DMASTSimplifier(); VerbosePrint("Constant folding"); astSimplifier.SimplifyAST(astFile); @@ -190,7 +188,7 @@ public static void Emit(CompilerEmission emission) { /// Emits the given warning, according to its ErrorLevel as set in our config. /// True if the warning was an error, false if not. public static bool Emit(WarningCode code, Location loc, string message) { - ErrorLevel level = Config.errorConfig[code]; + ErrorLevel level = Config.ErrorConfig[code]; Emit(new CompilerEmission(level, code, loc, message)); return level == ErrorLevel.Error; } @@ -328,7 +326,7 @@ private static string SaveJson(List maps, string interfaceFile, st public static void DefineFatalErrors() { foreach (WarningCode code in Enum.GetValues()) { if((int)code < 1_000) { - Config.errorConfig[code] = ErrorLevel.Error; + Config.ErrorConfig[code] = ErrorLevel.Error; } } } @@ -338,32 +336,32 @@ public static void DefineFatalErrors() { /// public static void CheckAllPragmasWereSet() { foreach(WarningCode code in Enum.GetValues()) { - if (!Config.errorConfig.ContainsKey(code)) { + if (!Config.ErrorConfig.ContainsKey(code)) { ForcedWarning($"Warning #{(int)code:d4} '{code.ToString()}' was never declared as error, warning, notice, or disabled."); - Config.errorConfig.Add(code, ErrorLevel.Disabled); + Config.ErrorConfig.Add(code, ErrorLevel.Disabled); } } } public static void SetPragma(WarningCode code, ErrorLevel level) { - Config.errorConfig[code] = level; + Config.ErrorConfig[code] = level; } public static ErrorLevel CodeToLevel(WarningCode code) { - bool didFind = Config.errorConfig.TryGetValue(code, out var ret); + bool didFind = Config.ErrorConfig.TryGetValue(code, out var ret); DebugTools.Assert(didFind); return ret; } } public struct DMCompilerSettings { - public List Files = null; + public List? Files = null; public bool SuppressUnimplementedWarnings = false; public bool NoticesEnabled = false; public bool DumpPreprocessor = false; public bool NoStandard = false; public bool Verbose = false; - public Dictionary MacroDefines = null; + public Dictionary? MacroDefines = null; /// A user-provided pragma config file, if one was provided. public string? PragmaFileOverride = null; @@ -375,10 +373,11 @@ public DMCompilerSettings() { } } - class DMCompilerConfiguration { - public Dictionary errorConfig; - public DMCompilerConfiguration() { - errorConfig = new(Enum.GetValues().Length); + internal class DMCompilerConfiguration { + public readonly Dictionary ErrorConfig = new(Enum.GetValues().Length); + + public void Reset() { + ErrorConfig.Clear(); } } }