Skip to content

Commit

Permalink
Add support for #define FILE_DIR (#1474)
Browse files Browse the repository at this point in the history
* Add support for `#define FILE_DIR`

* Update DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs
  • Loading branch information
wixoaGit authored Oct 2, 2023
1 parent 3d3a5d4 commit 5496c1f
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 50 deletions.
28 changes: 25 additions & 3 deletions DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand All @@ -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<string>(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;
Expand Down
26 changes: 17 additions & 9 deletions DMCompiler/DM/Expressions/Constant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
75 changes: 37 additions & 38 deletions DMCompiler/DMCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,22 @@
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<string> ResourceDirectories => _resourceDirectories;

private static DMCompilerConfiguration Config;
private static readonly DMCompilerConfiguration Config = new();
private static readonly List<string> _resourceDirectories = new();
private static DateTime _compileStartTime;

public static bool Compile(DMCompilerSettings settings) {
ErrorCount = 0;
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;
Expand All @@ -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<DreamMapJson> 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);
}
Expand All @@ -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<string> files, Dictionary<string, string> macroDefines) {
DMPreprocessor? build() {
public static void AddResourceDirectory(string dir) {
dir = dir.Replace('\\', Path.DirectorySeparatorChar);

_resourceDirectories.Add(dir);
}

private static DMPreprocessor? Preprocess(List<string> files, Dictionary<string, string>? macroDefines) {
DMPreprocessor? Build() {
DMPreprocessor preproc = new DMPreprocessor(true);
if (macroDefines != null) {
foreach (var (key, value) in macroDefines) {
Expand Down Expand Up @@ -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);
}

Expand All @@ -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<Token> preprocessedTokens) {
Expand All @@ -154,11 +157,6 @@ private static bool Compile(IEnumerable<Token> preprocessedTokens) {
Emit(warning);
}

if (astFile is null) {
VerbosePrint("Parsing failed, exiting compilation");
return false;
}

DMASTSimplifier astSimplifier = new DMASTSimplifier();
VerbosePrint("Constant folding");
astSimplifier.SimplifyAST(astFile);
Expand Down Expand Up @@ -190,7 +188,7 @@ public static void Emit(CompilerEmission emission) {
/// <summary> Emits the given warning, according to its ErrorLevel as set in our config. </summary>
/// <returns> True if the warning was an error, false if not.</returns>
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;
}
Expand Down Expand Up @@ -328,7 +326,7 @@ private static string SaveJson(List<DreamMapJson> maps, string interfaceFile, st
public static void DefineFatalErrors() {
foreach (WarningCode code in Enum.GetValues<WarningCode>()) {
if((int)code < 1_000) {
Config.errorConfig[code] = ErrorLevel.Error;
Config.ErrorConfig[code] = ErrorLevel.Error;
}
}
}
Expand All @@ -338,32 +336,32 @@ public static void DefineFatalErrors() {
/// </summary>
public static void CheckAllPragmasWereSet() {
foreach(WarningCode code in Enum.GetValues<WarningCode>()) {
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<string> Files = null;
public List<string>? Files = null;
public bool SuppressUnimplementedWarnings = false;
public bool NoticesEnabled = false;
public bool DumpPreprocessor = false;
public bool NoStandard = false;
public bool Verbose = false;
public Dictionary<string, string> MacroDefines = null;
public Dictionary<string, string>? MacroDefines = null;
/// <summary> A user-provided pragma config file, if one was provided. </summary>
public string? PragmaFileOverride = null;

Expand All @@ -375,10 +373,11 @@ public DMCompilerSettings() {
}
}

class DMCompilerConfiguration {
public Dictionary<WarningCode, ErrorLevel> errorConfig;
public DMCompilerConfiguration() {
errorConfig = new(Enum.GetValues<WarningCode>().Length);
internal class DMCompilerConfiguration {
public readonly Dictionary<WarningCode, ErrorLevel> ErrorConfig = new(Enum.GetValues<WarningCode>().Length);

public void Reset() {
ErrorConfig.Clear();
}
}
}

0 comments on commit 5496c1f

Please sign in to comment.