diff --git a/DMCompiler/Compiler/DM/DMAST.cs b/DMCompiler/Compiler/DM/DMAST.cs index 59f0f5c246..791af423d7 100644 --- a/DMCompiler/Compiler/DM/DMAST.cs +++ b/DMCompiler/Compiler/DM/DMAST.cs @@ -618,6 +618,12 @@ public sealed class DMASTProcBlockInner : DMASTNode { /// public readonly DMASTProcStatement[] SetStatements; + /// + /// This is used to determine if we should flag for WarningCode.EmptyBlock, + /// which we don't do if there was a manually entered semicolon within. + /// + public bool NotCompletelyEmpty; + /// Initializes an empty block. public DMASTProcBlockInner(Location location) : base(location) { Statements = Array.Empty(); diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index 2bad848edb..c3838db4a5 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -523,6 +523,7 @@ public DMASTFile File() { var loc = Current().Location; if (Check(TokenType.DM_LeftCurlyBracket)) { DMASTProcBlockInner? block; + bool hadSemicolon = false; Whitespace(); Newline(); @@ -535,9 +536,13 @@ public DMASTFile File() { List setStatements = new(); // set statements are weird and must be held separately. do { - (List? stmts, List? setStmts) = ProcBlockInner(); // Hope you understand tuples - if (stmts is not null) statements.AddRange(stmts); - if (setStmts is not null) setStatements.AddRange(setStmts); + (List? stmts, List? setStmts, bool innerHadSemicolon) = ProcBlockInner(); // Hope you understand tuples + if (stmts is not null) + statements.AddRange(stmts); + if (setStmts is not null) + setStatements.AddRange(setStmts); + if (innerHadSemicolon) + hadSemicolon = innerHadSemicolon; if (!Check(TokenType.DM_RightCurlyBracket)) { Error(WarningCode.BadToken, "Expected end of braced block"); @@ -550,7 +555,10 @@ public DMASTFile File() { } } while (true); - block = new DMASTProcBlockInner(loc, statements.ToArray(), setStatements.ToArray()); + block = new DMASTProcBlockInner(loc, statements.ToArray(), setStatements.ToArray()) + { + NotCompletelyEmpty = hadSemicolon + }; } return block; @@ -564,13 +572,16 @@ public DMASTFile File() { if (Check(TokenType.DM_Indent)) { List statements = new(); List setStatements = new(); // set statements are weird and must be held separately. + bool hadSemicolon = false; do { - (List? statements, List? setStatements) blockInner = ProcBlockInner(); + (List? statements, List? setStatements, bool hadSemicolon) blockInner = ProcBlockInner(); if (blockInner.statements is not null) statements.AddRange(blockInner.statements); if (blockInner.setStatements is not null) setStatements.AddRange(blockInner.setStatements); + if (blockInner.hadSemicolon) + hadSemicolon = blockInner.hadSemicolon; if (!Check(TokenType.DM_Dedent)) { Error("Expected end of proc statement", throwException: false); @@ -581,17 +592,26 @@ public DMASTFile File() { } } while (true); - return new DMASTProcBlockInner(loc, statements.ToArray(), setStatements.ToArray()); + return new DMASTProcBlockInner(loc, statements.ToArray(), setStatements.ToArray()) + { + NotCompletelyEmpty = hadSemicolon + }; } return null; } - public (List?, List?) ProcBlockInner() { + /// + /// Parses the inner of a proc block + /// + /// We return if there was a semicolon within for WarningCode.EmptyBlock + /// (list of proc statements, list of set statements, had a semicolon within) + public (List?, List?, bool) ProcBlockInner() { List procStatements = new(); List setStatements = new(); // We have to store them separately because they're evaluated first DMASTProcStatement? statement = null; + bool hadSemicolon = false; do { Whitespace(); @@ -611,11 +631,14 @@ public DMASTFile File() { DMASTProcBlockInner? blockInner = ProcBlock(); if (blockInner != null) procStatements.AddRange(blockInner.Statements); } - } while (Delimiter() || statement is DMASTProcStatementLabel); + } while (DelimiterHasSemicolon(ref hadSemicolon) || statement is DMASTProcStatementLabel); + Whitespace(); - if (procStatements.Count == 0) return (null,null); - return (procStatements, setStatements); + if (procStatements.Count == 0) + return (null, null, hadSemicolon); + + return (procStatements, setStatements, hadSemicolon); } public DMASTProcStatement? ProcStatement() { @@ -2617,5 +2640,19 @@ private bool Delimiter() { return hasDelimiter; } + + private bool DelimiterHasSemicolon(ref bool hasSemicolon) { + bool hasDelimiter = false; + while ( + // Once it is set to true, keep it that way + (!hasSemicolon && (hasSemicolon = Current().Type == TokenType.DM_Semicolon)) + || Check(TokenType.DM_Semicolon) + || Newline() + ) { + hasDelimiter = true; + } + + return hasDelimiter; + } } } diff --git a/DMCompiler/DM/Visitors/DMProcBuilder.cs b/DMCompiler/DM/Visitors/DMProcBuilder.cs index 14e540c08f..136f574510 100644 --- a/DMCompiler/DM/Visitors/DMProcBuilder.cs +++ b/DMCompiler/DM/Visitors/DMProcBuilder.cs @@ -85,7 +85,7 @@ private void ProcessBlockInner(DMASTProcBlockInner block, bool silenceEmptyBlock DMCompiler.Emit(e.Error); } } - if(!silenceEmptyBlockWarning && block.Statements.Length == 0) { // If this block has no real statements + if(!silenceEmptyBlockWarning && block.Statements.Length == 0 && !block.NotCompletelyEmpty) { // If this block has no real statements // Not an error in BYOND, but we do have an emission for this! if(block.SetStatements.Length != 0) { // Give a more articulate message about this, since it's kinda weird DMCompiler.Emit(WarningCode.EmptyBlock,block.Location,"Empty block detected - set statements are executed outside of, before, and unconditional to, this block");