diff --git a/.github/workflows/compiler-test.yml b/.github/workflows/compiler-test.yml index 85427e0a66..60ec6de731 100644 --- a/.github/workflows/compiler-test.yml +++ b/.github/workflows/compiler-test.yml @@ -47,7 +47,7 @@ jobs: - name: Compile /tg/station Master run: main\bin\DMCompiler\DMCompiler.exe tg\tgstation.dme --suppress-unimplemented - name: Disassemble /tg/station Master - run: main\bin\DMDisassembler\DMDisassembler.exe tg\tgstation.json crash-on-error + run: main\bin\DMDisassembler\DMDisassembler.exe tg\tgstation.json crash-on-test - name: Checkout Goonstation Master uses: actions/checkout@v2 with: diff --git a/Content.IntegrationTests/Content.IntegrationTests.csproj b/Content.IntegrationTests/Content.IntegrationTests.csproj index 820b1737a7..737388f6d2 100644 --- a/Content.IntegrationTests/Content.IntegrationTests.csproj +++ b/Content.IntegrationTests/Content.IntegrationTests.csproj @@ -8,6 +8,8 @@ 12 NU1507 true + Content.IntegrationTests + Content.IntegrationTests diff --git a/Content.IntegrationTests/SetupCompileDM.cs b/Content.IntegrationTests/SetupCompileDM.cs index ff532c0ddb..bb15fab3cb 100644 --- a/Content.IntegrationTests/SetupCompileDM.cs +++ b/Content.IntegrationTests/SetupCompileDM.cs @@ -15,7 +15,8 @@ public sealed class SetupCompileDm { [OneTimeSetUp] public void Compile() { - bool successfulCompile = DMCompiler.DMCompiler.Compile(new() { + DMCompiler.DMCompiler compiler = new(); + bool successfulCompile = compiler.Compile(new() { Files = new() { DmEnvironment } }); diff --git a/Content.Tests/Content.Tests.csproj b/Content.Tests/Content.Tests.csproj index a6ddc575d8..29f7538883 100644 --- a/Content.Tests/Content.Tests.csproj +++ b/Content.Tests/Content.Tests.csproj @@ -11,6 +11,8 @@ enable NU1507 true + Content.Tests + Content.Tests diff --git a/Content.Tests/DMProject/Tests/Builtins/world_opendream_topic_port.dm b/Content.Tests/DMProject/Tests/Builtins/world_opendream_topic_port.dm new file mode 100644 index 0000000000..a815651f3f --- /dev/null +++ b/Content.Tests/DMProject/Tests/Builtins/world_opendream_topic_port.dm @@ -0,0 +1,3 @@ + +/proc/RunTest() + ASSERT(isnum(world.opendream_topic_port)) diff --git a/Content.Tests/DMProject/Tests/Builtins/world_system_type.dm b/Content.Tests/DMProject/Tests/Builtins/world_system_type.dm new file mode 100644 index 0000000000..ea9ce6b505 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Builtins/world_system_type.dm @@ -0,0 +1,3 @@ + +/proc/RunTest() + ASSERT(istext(world.system_type) && length(world.system_type)) diff --git a/Content.Tests/DMProject/Tests/Dereference/RuntimeSearchOperatorLint.dm b/Content.Tests/DMProject/Tests/Dereference/RuntimeSearchOperatorLint.dm new file mode 100644 index 0000000000..40c751a656 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Dereference/RuntimeSearchOperatorLint.dm @@ -0,0 +1,8 @@ +// COMPILE ERROR OD3300 +#pragma RuntimeSearchOperator error +/datum/proc/foo() + return + +/proc/RunTest() + var/datum/D = new + D:foo() diff --git a/Content.Tests/DMProject/Tests/Tree/upward_search_path3.dm b/Content.Tests/DMProject/Tests/Tree/upward_search_path3.dm new file mode 100644 index 0000000000..f68195b649 --- /dev/null +++ b/Content.Tests/DMProject/Tests/Tree/upward_search_path3.dm @@ -0,0 +1,8 @@ + +//# issue 617 + +/datum/foo +/datum/bar/var/meep = .foo +/proc/RunTest() + var/datum/bar/D = new + ASSERT(D.meep == /datum/foo) diff --git a/Content.Tests/DMTests.cs b/Content.Tests/DMTests.cs index c8de51f562..684534e7e2 100644 --- a/Content.Tests/DMTests.cs +++ b/Content.Tests/DMTests.cs @@ -37,16 +37,17 @@ public enum DMTestFlags { [OneTimeSetUp] public void OneTimeSetup() { + var dmCompiler = new DMCompiler.DMCompiler(); IoCManager.InjectDependencies(this); _taskManager.Initialize(); - Compile(InitializeEnvironment); + Compile(dmCompiler, InitializeEnvironment); _dreamMan.PreInitialize(Path.ChangeExtension(InitializeEnvironment, "json")); _dreamMan.OnException += OnException; } - private static string? Compile(string sourceFile) { - bool successfulCompile = DMCompiler.DMCompiler.Compile(new() { - Files = new() { sourceFile } + private string? Compile(DMCompiler.DMCompiler compiler, string sourceFile) { + bool successfulCompile = compiler.Compile(new() { + Files = [sourceFile] }); return successfulCompile ? Path.ChangeExtension(sourceFile, "json") : null; @@ -64,10 +65,11 @@ public void TestFiles(string sourceFile, DMTestFlags testFlags, int errorCode) { string initialDirectory = Directory.GetCurrentDirectory(); TestContext.WriteLine($"--- TEST {sourceFile} | Flags: {testFlags}"); try { - string? compiledFile = Compile(Path.Join(initialDirectory, TestsDirectory, sourceFile)); + var dmCompiler = new DMCompiler.DMCompiler(); + var compiledFile = Compile(dmCompiler, Path.Join(initialDirectory, TestsDirectory, sourceFile)); if (testFlags.HasFlag(DMTestFlags.CompileError)) { Assert.That(errorCode == -1, Is.False, "Expected an error code"); - Assert.That(DMCompiler.DMCompiler.UniqueEmissions.Contains((WarningCode)errorCode), Is.True, $"Expected error code \"{errorCode}\" was not found"); + Assert.That(dmCompiler.UniqueEmissions.Contains((WarningCode)errorCode), Is.True, $"Expected error code \"{errorCode}\" was not found"); Assert.That(compiledFile, Is.Null, "Expected an error during DM compilation"); Cleanup(compiledFile); @@ -167,29 +169,28 @@ private static DMTestFlags GetDMTestFlags(string sourceFile, out int errorCode) DMTestFlags testFlags = DMTestFlags.NoError; errorCode = -1; // If it's null GetTests() fusses about a NRE - using (StreamReader reader = new StreamReader(sourceFile)) { - string? firstLine = reader.ReadLine(); - if (firstLine == null) - return testFlags; - if (firstLine.Contains("IGNORE", StringComparison.InvariantCulture)) - testFlags |= DMTestFlags.Ignore; - if (firstLine.Contains("COMPILE ERROR", StringComparison.InvariantCulture)) { - testFlags |= DMTestFlags.CompileError; - - Match match = ErrorCodeRegex().Match(firstLine); // "OD" followed by exactly 4 numbers - if (match.Success) { - errorCode = int.Parse(match.Groups[1].Value); - } + using StreamReader reader = new StreamReader(sourceFile); + string? firstLine = reader.ReadLine(); + if (firstLine == null) + return testFlags; + if (firstLine.Contains("IGNORE", StringComparison.InvariantCulture)) + testFlags |= DMTestFlags.Ignore; + if (firstLine.Contains("COMPILE ERROR", StringComparison.InvariantCulture)) { + testFlags |= DMTestFlags.CompileError; + + Match match = ErrorCodeRegex().Match(firstLine); // "OD" followed by exactly 4 numbers + if (match.Success) { + errorCode = int.Parse(match.Groups[1].Value); } - - if (firstLine.Contains("RUNTIME ERROR", StringComparison.InvariantCulture)) - testFlags |= DMTestFlags.RuntimeError; - if (firstLine.Contains("RETURN TRUE", StringComparison.InvariantCulture)) - testFlags |= DMTestFlags.ReturnTrue; - if (firstLine.Contains("NO RETURN", StringComparison.InvariantCulture)) - testFlags |= DMTestFlags.NoReturn; } + if (firstLine.Contains("RUNTIME ERROR", StringComparison.InvariantCulture)) + testFlags |= DMTestFlags.RuntimeError; + if (firstLine.Contains("RETURN TRUE", StringComparison.InvariantCulture)) + testFlags |= DMTestFlags.ReturnTrue; + if (firstLine.Contains("NO RETURN", StringComparison.InvariantCulture)) + testFlags |= DMTestFlags.NoReturn; + return testFlags; } diff --git a/DMCompiler/Bytecode/DMReference.cs b/DMCompiler/Bytecode/DMReference.cs index 775f34235b..ae63c782f7 100644 --- a/DMCompiler/Bytecode/DMReference.cs +++ b/DMCompiler/Bytecode/DMReference.cs @@ -7,6 +7,7 @@ public struct DMReference { public static readonly DMReference Self = new() { RefType = Type.Self }; public static readonly DMReference Usr = new() { RefType = Type.Usr }; public static readonly DMReference Args = new() { RefType = Type.Args }; + public static readonly DMReference World = new() { RefType = Type.World }; public static readonly DMReference SuperProc = new() { RefType = Type.SuperProc }; public static readonly DMReference ListIndex = new() { RefType = Type.ListIndex }; public static readonly DMReference Invalid = new() { RefType = Type.Invalid }; @@ -16,6 +17,7 @@ public enum Type : byte { Self, Usr, Args, + World, SuperProc, ListIndex, Argument, diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs index bf6ddc94ae..3d5d36dfaa 100644 --- a/DMCompiler/Bytecode/DreamProcOpcode.cs +++ b/DMCompiler/Bytecode/DreamProcOpcode.cs @@ -95,7 +95,8 @@ public enum DreamProcOpcode : byte { CreateObject = 0x2E, [OpcodeMetadata(-1, OpcodeArgType.Label)] BooleanOr = 0x2F, // Shrinks the stack by 1 or 0. Assume 1. - //0x30 + [OpcodeMetadata(0, OpcodeArgType.ListSize)] + CreateMultidimensionalList = 0x30, [OpcodeMetadata(-1)] CompareGreaterThanOrEqual = 0x31, [OpcodeMetadata(-1, OpcodeArgType.Label)] diff --git a/DMCompiler/Compiler/CompilerError.cs b/DMCompiler/Compiler/CompilerError.cs index 92781d038e..f23dc9a2df 100644 --- a/DMCompiler/Compiler/CompilerError.cs +++ b/DMCompiler/Compiler/CompilerError.cs @@ -50,6 +50,7 @@ public enum WarningCode { PointlessScopeOperator = 2209, PointlessPositionalArgument = 2210, ProcArgumentGlobal = 2211, // Prepending "/" on a proc arg (e.g. "/proc/example(/var/foo)" makes the arg a global var. Ref https://www.byond.com/forum/post/2830750 + AmbiguousVarStatic = 2212, // Referencing a static variable when an instance variable with the same name exists MalformedRange = 2300, InvalidRange = 2301, InvalidSetStatement = 2302, @@ -72,7 +73,8 @@ public enum WarningCode { SuspiciousSwitchCase = 3201, // "else if" cases are actually valid DM, they just spontaneously end the switch context and begin an if-else ladder within the else case of the switch AssignmentInConditional = 3202, PickWeightedSyntax = 3203, - AmbiguousInOrder = 3204 + AmbiguousInOrder = 3204, + RuntimeSearchOperator = 3300, // 4000 - 4999 are reserved for runtime configuration. (TODO: Runtime doesn't know about configs yet!) } @@ -116,10 +118,3 @@ public CompilerEmission(ErrorLevel level, WarningCode code, Location? location, _ => "", }; } - -// TODO: Find a nicer way to do this -public sealed class UnknownIdentifierException(Location location, string identifier) - : Exception($"Unknown identifier \"{identifier}\" - This message should not be seen") { - public readonly Location Location = location; - public readonly string Identifier = identifier; -} diff --git a/DMCompiler/Compiler/DM/AST/DMAST.Expression.cs b/DMCompiler/Compiler/DM/AST/DMAST.Expression.cs index a879b380fb..f880bfe587 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.Expression.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.Expression.cs @@ -15,6 +15,13 @@ public virtual IEnumerable Leaves() { public virtual DMASTExpression GetUnwrapped() { return this; } + + public override string ToStringNoLocation() { + var leaves = Leaves().ToList(); + if (leaves.Count == 0) + return $"{GetType().Name}"; + return $"{GetType().Name}({string.Join(", ", Leaves().Select(l => l.ToString(Location)))})"; + } } /// diff --git a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs index 6c6aa57898..3d556981fa 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.ObjectStatements.cs @@ -67,9 +67,6 @@ public sealed class DMASTObjectVarDefinition( public bool IsStatic => _varDecl.IsStatic; - // TODO: Standardize our phrasing in the codebase. Are we calling these Statics or Globals? - public bool IsGlobal => _varDecl.IsStatic; - public bool IsConst => _varDecl.IsConst; public bool IsTmp => _varDecl.IsTmp; diff --git a/DMCompiler/Compiler/DM/AST/DMAST.cs b/DMCompiler/Compiler/DM/AST/DMAST.cs index 4092e2f2a8..993e2b28fa 100644 --- a/DMCompiler/Compiler/DM/AST/DMAST.cs +++ b/DMCompiler/Compiler/DM/AST/DMAST.cs @@ -4,6 +4,20 @@ namespace DMCompiler.Compiler.DM.AST; public abstract class DMASTNode(Location location) { public readonly Location Location = location; + + public override string ToString() { + return $"{ToString(null)}"; + } + + public string ToString(Location? loc) { + if (loc is not null && Location.SourceFile == loc.Value.SourceFile && Location.Line == loc.Value.Line) + return ToStringNoLocation(); + return $"{ToStringNoLocation()} [{Location}]"; + } + + public virtual string ToStringNoLocation() { + return GetType().Name; + } } public sealed class DMASTFile(Location location, DMASTBlockInner blockInner) : DMASTNode(location) { diff --git a/DMCompiler/Compiler/DM/DMLexer.cs b/DMCompiler/Compiler/DM/DMLexer.cs index d56495fd6b..8e4e710034 100644 --- a/DMCompiler/Compiler/DM/DMLexer.cs +++ b/DMCompiler/Compiler/DM/DMLexer.cs @@ -3,7 +3,7 @@ namespace DMCompiler.Compiler.DM; -public sealed class DMLexer : TokenLexer { +internal sealed class DMLexer : TokenLexer { public static readonly List ValidEscapeSequences = [ "icon", "Roman", "roman", @@ -68,7 +68,7 @@ public sealed class DMLexer : TokenLexer { private readonly Stack _indentationStack = new(new[] { 0 }); /// The enumerable list of tokens output by . - public DMLexer(string sourceName, IEnumerable source) : base(sourceName, source) { } + internal DMLexer(string sourceName, IEnumerable source) : base(sourceName, source) { } protected override Token ParseNextToken() { Token token; @@ -114,6 +114,7 @@ protected override Token ParseNextToken() { token = preprocToken; } } else { + var firstTokenLocation = CurrentLocation; switch (preprocToken.Type) { case TokenType.DM_Preproc_Whitespace: Advance(); token = CreateToken(TokenType.DM_Whitespace, preprocToken.Text); break; case TokenType.DM_Preproc_Punctuator_LeftParenthesis: BracketNesting++; Advance(); token = CreateToken(TokenType.DM_LeftParenthesis, preprocToken.Text); break; @@ -125,19 +126,19 @@ protected override Token ParseNextToken() { case TokenType.DM_Preproc_Punctuator_Question: switch (Advance().Type) { case TokenType.DM_Preproc_Punctuator_Period: - token = CreateToken(TokenType.DM_QuestionPeriod, "?."); Advance(); + token = CreateToken(TokenType.DM_QuestionPeriod, "?.", firstTokenLocation); break; case TokenType.DM_Preproc_Punctuator_Colon: - token = CreateToken(TokenType.DM_QuestionColon, "?:"); Advance(); + token = CreateToken(TokenType.DM_QuestionColon, "?:", firstTokenLocation); break; case TokenType.DM_Preproc_Punctuator_LeftBracket: - token = CreateToken(TokenType.DM_QuestionLeftBracket, "?["); - BracketNesting++; Advance(); + token = CreateToken(TokenType.DM_QuestionLeftBracket, "?[", firstTokenLocation); + BracketNesting++; break; default: @@ -149,11 +150,10 @@ protected override Token ParseNextToken() { switch (Advance().Type) { case TokenType.DM_Preproc_Punctuator_Period: if (Advance().Type == TokenType.DM_Preproc_Punctuator_Period) { - token = CreateToken(TokenType.DM_IndeterminateArgs, "..."); - Advance(); + token = CreateToken(TokenType.DM_IndeterminateArgs, "...", firstTokenLocation); } else { - token = CreateToken(TokenType.DM_SuperProc, ".."); + token = CreateToken(TokenType.DM_SuperProc, "..", firstTokenLocation); } break; @@ -231,6 +231,7 @@ protected override Token ParseNextToken() { break; } case TokenType.DM_Preproc_ConstantString: { + Advance(); string tokenText = preprocToken.Text; switch (preprocToken.Text[0]) { case '"': @@ -239,21 +240,19 @@ protected override Token ParseNextToken() { case '@': token = CreateToken(TokenType.DM_RawString, tokenText, preprocToken.Value); break; default: token = CreateToken(TokenType.Error, tokenText, "Invalid string"); break; } - - Advance(); break; } case TokenType.DM_Preproc_StringBegin: - token = CreateToken(TokenType.DM_StringBegin, preprocToken.Text, preprocToken.Value); Advance(); + token = CreateToken(TokenType.DM_StringBegin, preprocToken.Text, preprocToken.Value); break; case TokenType.DM_Preproc_StringMiddle: - token = CreateToken(TokenType.DM_StringMiddle, preprocToken.Text, preprocToken.Value); Advance(); + token = CreateToken(TokenType.DM_StringMiddle, preprocToken.Text, preprocToken.Value); break; case TokenType.DM_Preproc_StringEnd: - token = CreateToken(TokenType.DM_StringEnd, preprocToken.Text, preprocToken.Value); Advance(); + token = CreateToken(TokenType.DM_StringEnd, preprocToken.Text, preprocToken.Value); break; case TokenType.DM_Preproc_Identifier: { TokenTextBuilder.Clear(); @@ -267,7 +266,7 @@ protected override Token ParseNextToken() { var identifierText = TokenTextBuilder.ToString(); var tokenType = Keywords.GetValueOrDefault(identifierText, TokenType.DM_Identifier); - token = CreateToken(tokenType, identifierText); + token = CreateToken(tokenType, identifierText, firstTokenLocation); break; } case TokenType.DM_Preproc_Number: { @@ -290,7 +289,7 @@ protected override Token ParseNextToken() { break; } - case TokenType.EndOfFile: token = preprocToken; Advance(); break; + case TokenType.EndOfFile: Advance(); token = preprocToken; break; default: token = CreateToken(TokenType.Error, preprocToken.Text, "Invalid token"); break; } } diff --git a/DMCompiler/Compiler/DM/DMParser.cs b/DMCompiler/Compiler/DM/DMParser.cs index bf7e025353..00c566fd16 100644 --- a/DMCompiler/Compiler/DM/DMParser.cs +++ b/DMCompiler/Compiler/DM/DMParser.cs @@ -4,7 +4,7 @@ using DMCompiler.DM; namespace DMCompiler.Compiler.DM { - public partial class DMParser(DMLexer lexer) : Parser(lexer) { + internal partial class DMParser(DMCompiler compiler, DMLexer lexer) : Parser(compiler, lexer) { protected Location CurrentLoc => Current().Location; protected DreamPath CurrentPath = DreamPath.Root; @@ -211,7 +211,7 @@ public DMASTFile File() { //Proc definition if (Check(TokenType.DM_LeftParenthesis)) { - DMCompiler.VerbosePrint($"Parsing proc {CurrentPath}()"); + Compiler.VerbosePrint($"Parsing proc {CurrentPath}()"); BracketWhitespace(); var parameters = DefinitionParameters(out var wasIndeterminate); @@ -249,7 +249,7 @@ public DMASTFile File() { } if (procBlock?.Statements.Length is 0 or null) { - DMCompiler.Emit(WarningCode.EmptyProc, loc, + Compiler.Emit(WarningCode.EmptyProc, loc, "Empty proc detected - add an explicit \"return\" statement"); } @@ -270,7 +270,7 @@ public DMASTFile File() { //Object definition if (Block() is { } block) { - DMCompiler.VerbosePrint($"Parsed object {CurrentPath}"); + Compiler.VerbosePrint($"Parsed object {CurrentPath}"); return new DMASTObjectDefinition(loc, CurrentPath, block); } @@ -337,7 +337,7 @@ public DMASTFile File() { } //Empty object definition - DMCompiler.VerbosePrint($"Parsed object {CurrentPath}"); + Compiler.VerbosePrint($"Parsed object {CurrentPath} - empty"); return new DMASTObjectDefinition(loc, CurrentPath, null); } @@ -385,12 +385,12 @@ public DMASTFile File() { } } else if (Check(OperatorOverloadTypes)) { if (operatorToken is { Type: TokenType.DM_ConstantString, Value: not "" }) { - DMCompiler.Emit(WarningCode.BadToken, operatorToken.Location, + Compiler.Emit(WarningCode.BadToken, operatorToken.Location, "The quotes in a stringify overload must be empty"); } if (!ImplementedOperatorOverloadTypes.Contains(operatorToken.Type)) { - DMCompiler.UnimplementedWarning(operatorToken.Location, + Compiler.UnimplementedWarning(operatorToken.Location, $"operator{operatorToken.PrintableText} overloads are not implemented. They will be defined but never called."); } @@ -475,7 +475,7 @@ public DMASTFile File() { do { var identifier = Identifier(); if (identifier == null) { - DMCompiler.Emit(WarningCode.BadToken, Current().Location, "Identifier expected"); + Compiler.Emit(WarningCode.BadToken, Current().Location, "Identifier expected"); return null; } @@ -747,22 +747,24 @@ public DMASTFile File() { return new DMASTProcStatementExpression(loc, expression); } else { - // These are sorted by frequency - DMASTProcStatement? procStatement = If(); - procStatement ??= Return(); - procStatement ??= ProcVarDeclaration(); - procStatement ??= For(); - procStatement ??= Set(); - procStatement ??= Switch(); - procStatement ??= Continue(); - procStatement ??= Break(); - procStatement ??= Spawn(); - procStatement ??= While(); - procStatement ??= DoWhile(); - procStatement ??= Throw(); - procStatement ??= Del(); - procStatement ??= TryCatch(); - procStatement ??= Goto(); + DMASTProcStatement? procStatement = Current().Type switch { + TokenType.DM_If => If(), + TokenType.DM_Return => Return(), + TokenType.DM_For => For(), + TokenType.DM_Set => Set(), + TokenType.DM_Switch => Switch(), + TokenType.DM_Continue => Continue(), + TokenType.DM_Break => Break(), + TokenType.DM_Spawn => Spawn(), + TokenType.DM_While => While(), + TokenType.DM_Do => DoWhile(), + TokenType.DM_Throw => Throw(), + TokenType.DM_Del => Del(), + TokenType.DM_Try => TryCatch(), + TokenType.DM_Goto => Goto(), + TokenType.DM_Slash or TokenType.DM_Var => ProcVarDeclaration(), + _ => null + }; if (procStatement != null) { Whitespace(); @@ -992,279 +994,252 @@ private DMASTProcStatementSet[] ProcSetEnd(bool allowMultiple) { return setDeclarations.ToArray(); } - private DMASTProcStatementReturn? Return() { + private DMASTProcStatementReturn Return() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Return)) { - Whitespace(); - DMASTExpression? value = Expression(); + Whitespace(); + DMASTExpression? value = Expression(); - return new DMASTProcStatementReturn(loc, value); - } else { - return null; - } + return new DMASTProcStatementReturn(loc, value); } private DMASTProcStatementBreak? Break() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Break)) { - Whitespace(); - DMASTIdentifier? label = Identifier(); + Whitespace(); + DMASTIdentifier? label = Identifier(); - return new DMASTProcStatementBreak(loc, label); - } else { - return null; - } + return new DMASTProcStatementBreak(loc, label); } - private DMASTProcStatementContinue? Continue() { + private DMASTProcStatementContinue Continue() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Continue)) { - Whitespace(); - DMASTIdentifier? label = Identifier(); + Whitespace(); + DMASTIdentifier? label = Identifier(); - return new DMASTProcStatementContinue(loc, label); - } else { - return null; - } + return new DMASTProcStatementContinue(loc, label); } - private DMASTProcStatement? Goto() { + private DMASTProcStatement Goto() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Goto)) { - Whitespace(); - DMASTIdentifier? label = Identifier(); - - if (label == null) { - Emit(WarningCode.BadToken, "Expected a label"); - return new DMASTInvalidProcStatement(loc); - } + Whitespace(); + DMASTIdentifier? label = Identifier(); - return new DMASTProcStatementGoto(loc, label); - } else { - return null; + if (label == null) { + Emit(WarningCode.BadToken, "Expected a label"); + return new DMASTInvalidProcStatement(loc); } + + return new DMASTProcStatementGoto(loc, label); } - private DMASTProcStatementDel? Del() { + private DMASTProcStatementDel Del() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Del)) { - Whitespace(); - bool hasParenthesis = Check(TokenType.DM_LeftParenthesis); - Whitespace(); - DMASTExpression? value = Expression(); - RequireExpression(ref value, "Expected value to delete"); - if (hasParenthesis) ConsumeRightParenthesis(); + Whitespace(); + bool hasParenthesis = Check(TokenType.DM_LeftParenthesis); + Whitespace(); + DMASTExpression? value = Expression(); + RequireExpression(ref value, "Expected value to delete"); + if (hasParenthesis) ConsumeRightParenthesis(); - return new DMASTProcStatementDel(loc, value); - } else { - return null; - } + return new DMASTProcStatementDel(loc, value); } /// Either a or a DMASTAggregate that acts as a container for them. May be null. - private DMASTProcStatement? Set() { + private DMASTProcStatement Set() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Set)) { - Whitespace(); - - DMASTProcStatementSet[] sets = ProcSetEnd(true); - if (sets.Length == 0) { - Emit(WarningCode.InvalidSetStatement, "Expected set declaration"); - return new DMASTInvalidProcStatement(loc); - } + Whitespace(); - if (sets.Length > 1) - return new DMASTAggregate(loc, sets); - return sets[0]; + DMASTProcStatementSet[] sets = ProcSetEnd(true); + if (sets.Length == 0) { + Emit(WarningCode.InvalidSetStatement, "Expected set declaration"); + return new DMASTInvalidProcStatement(loc); } - return null; + if (sets.Length > 1) + return new DMASTAggregate(loc, sets); + return sets[0]; } - private DMASTProcStatementSpawn? Spawn() { + private DMASTProcStatementSpawn Spawn() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Spawn)) { - Whitespace(); - bool hasArg = Check(TokenType.DM_LeftParenthesis); - DMASTExpression? delay = null; - - if (hasArg) { - Whitespace(); + Whitespace(); + bool hasArg = Check(TokenType.DM_LeftParenthesis); + DMASTExpression? delay = null; - if (!Check(TokenType.DM_RightParenthesis)) { - delay = Expression(); - RequireExpression(ref delay, "Expected a delay"); + if (hasArg) { + Whitespace(); - ConsumeRightParenthesis(); - } + if (!Check(TokenType.DM_RightParenthesis)) { + delay = Expression(); + RequireExpression(ref delay, "Expected a delay"); - Whitespace(); + ConsumeRightParenthesis(); } - Newline(); + Whitespace(); + } - DMASTProcBlockInner? body = ProcBlock(); - if (body == null) { - DMASTProcStatement? statement = ProcStatement(); + Newline(); - if (statement != null) { - body = new DMASTProcBlockInner(loc, statement); - } else { - Emit(WarningCode.MissingBody, "Expected body or statement"); - body = new DMASTProcBlockInner(loc); - } - } + DMASTProcBlockInner? body = ProcBlock(); + if (body == null) { + DMASTProcStatement? statement = ProcStatement(); - return new DMASTProcStatementSpawn(loc, delay ?? new DMASTConstantInteger(loc, 0), body); - } else { - return null; + if (statement != null) { + body = new DMASTProcBlockInner(loc, statement); + } else { + Emit(WarningCode.MissingBody, "Expected body or statement"); + body = new DMASTProcBlockInner(loc); + } } + + return new DMASTProcStatementSpawn(loc, delay ?? new DMASTConstantInteger(loc, 0), body); } - private DMASTProcStatementIf? If() { + private DMASTProcStatementIf If() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_If)) { - Whitespace(); - Consume(TokenType.DM_LeftParenthesis, "Expected '('"); - BracketWhitespace(); - DMASTExpression? condition = Expression(); - RequireExpression(ref condition, "Expected a condition"); + Whitespace(); + Consume(TokenType.DM_LeftParenthesis, "Expected '('"); + BracketWhitespace(); + DMASTExpression? condition = Expression(); + RequireExpression(ref condition, "Expected a condition"); - if (condition is DMASTAssign) { - Emit(WarningCode.AssignmentInConditional, condition.Location, "Assignment in conditional"); - } + if (condition is DMASTAssign) { + Emit(WarningCode.AssignmentInConditional, condition.Location, "Assignment in conditional"); + } - BracketWhitespace(); - ConsumeRightParenthesis(); + BracketWhitespace(); + ConsumeRightParenthesis(); + Whitespace(); + Check(TokenType.DM_Colon); + Whitespace(); + + DMASTProcStatement? procStatement = ProcStatement(); + DMASTProcBlockInner? elseBody = null; + DMASTProcBlockInner? body = (procStatement != null) + ? new DMASTProcBlockInner(loc, procStatement) + : ProcBlock(); + body ??= new DMASTProcBlockInner(loc); + + Token afterIfBody = Current(); + bool newLineAfterIf = Delimiter(); + if (newLineAfterIf) Whitespace(); + if (Check(TokenType.DM_Else)) { Whitespace(); Check(TokenType.DM_Colon); Whitespace(); + procStatement = ProcStatement(); - DMASTProcStatement? procStatement = ProcStatement(); - DMASTProcBlockInner? elseBody = null; - DMASTProcBlockInner? body = (procStatement != null) + elseBody = (procStatement != null) ? new DMASTProcBlockInner(loc, procStatement) : ProcBlock(); - body ??= new DMASTProcBlockInner(loc); - - Token afterIfBody = Current(); - bool newLineAfterIf = Delimiter(); - if (newLineAfterIf) Whitespace(); - if (Check(TokenType.DM_Else)) { - Whitespace(); - Check(TokenType.DM_Colon); - Whitespace(); - procStatement = ProcStatement(); - - elseBody = (procStatement != null) - ? new DMASTProcBlockInner(loc, procStatement) - : ProcBlock(); - elseBody ??= new DMASTProcBlockInner(loc); - } else if (newLineAfterIf) { - ReuseToken(afterIfBody); - } - - return new DMASTProcStatementIf(loc, condition, body, elseBody); - } else { - return null; + elseBody ??= new DMASTProcBlockInner(loc); + } else if (newLineAfterIf) { + ReuseToken(afterIfBody); } + + return new DMASTProcStatementIf(loc, condition, body, elseBody); } - private DMASTProcStatement? For() { + private DMASTProcStatement For() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_For)) { - Whitespace(); - Consume(TokenType.DM_LeftParenthesis, "Expected '('"); - Whitespace(); - - if (Check(TokenType.DM_RightParenthesis)) { - return new DMASTProcStatementInfLoop(loc, GetForBody(loc)); - } + Whitespace(); + Consume(TokenType.DM_LeftParenthesis, "Expected '('"); + Whitespace(); - _allowVarDeclExpression = true; - DMASTExpression? expr1 = Expression(); - DMComplexValueType? dmTypes = AsComplexTypes(); - Whitespace(); - _allowVarDeclExpression = false; - if (expr1 == null) { - if (!ForSeparatorTypes.Contains(Current().Type)) { - Emit(WarningCode.BadExpression, "Expected 1st expression in for"); - } + if (Check(TokenType.DM_RightParenthesis)) { + return new DMASTProcStatementInfLoop(loc, GetForBody(loc)); + } - expr1 = new DMASTConstantNull(loc); + _allowVarDeclExpression = true; + DMASTExpression? expr1 = Expression(); + DMComplexValueType? dmTypes = AsComplexTypes(); + Whitespace(); + _allowVarDeclExpression = false; + if (expr1 == null) { + if (!ForSeparatorTypes.Contains(Current().Type)) { + Emit(WarningCode.BadExpression, "Expected 1st expression in for"); } - if (Check(TokenType.DM_To)) { - if (expr1 is DMASTAssign assign) { - ExpressionTo(out var endRange, out var step); - Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after to expression"); - return new DMASTProcStatementFor(loc, new DMASTExpressionInRange(loc, assign.LHS, assign.RHS, endRange, step), null, null, dmTypes, GetForBody(loc)); - } else { - Emit(WarningCode.BadExpression, "Expected = before to in for"); - return new DMASTInvalidProcStatement(loc); - } - } + expr1 = new DMASTConstantNull(loc); + } - if (Check(TokenType.DM_In)) { - Whitespace(); - DMASTExpression? listExpr = Expression(); - Whitespace(); - Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 2"); - return new DMASTProcStatementFor(loc, new DMASTExpressionIn(loc, expr1, listExpr), null, null, dmTypes, GetForBody(loc)); + if (Check(TokenType.DM_To)) { + if (expr1 is DMASTAssign assign) { + ExpressionTo(out var endRange, out var step); + Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after to expression"); + return new DMASTProcStatementFor(loc, new DMASTExpressionInRange(loc, assign.LHS, assign.RHS, endRange, step), null, null, dmTypes, GetForBody(loc)); + } else { + Emit(WarningCode.BadExpression, "Expected = before to in for"); + return new DMASTInvalidProcStatement(loc); } + } - if (!Check(ForSeparatorTypes)) { - Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 1"); - return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc)); - } + if (Check(TokenType.DM_In)) { + Whitespace(); + DMASTExpression? listExpr = Expression(); + Whitespace(); + Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 2"); + return new DMASTProcStatementFor(loc, new DMASTExpressionIn(loc, expr1, listExpr), null, null, dmTypes, GetForBody(loc)); + } - if (Check(TokenType.DM_RightParenthesis)) { - return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc)); - } + if (!Check(ForSeparatorTypes)) { + Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 1"); + return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc)); + } - Whitespace(); - DMASTExpression? expr2 = Expression(); - if (expr2 == null) { - if (!ForSeparatorTypes.Contains(Current().Type)) { - Emit(WarningCode.BadExpression, "Expected 2nd expression in for"); - } + if (Check(TokenType.DM_RightParenthesis)) { + return new DMASTProcStatementFor(loc, expr1, null, null, dmTypes, GetForBody(loc)); + } - expr2 = new DMASTConstantInteger(loc, 1); + Whitespace(); + DMASTExpression? expr2 = Expression(); + if (expr2 == null) { + if (!ForSeparatorTypes.Contains(Current().Type)) { + Emit(WarningCode.BadExpression, "Expected 2nd expression in for"); } - if (!Check(ForSeparatorTypes)) { - Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 2"); - return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc)); - } + expr2 = new DMASTConstantInteger(loc, 1); + } - if (Check(TokenType.DM_RightParenthesis)) { - return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc)); - } + if (!Check(ForSeparatorTypes)) { + Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 2"); + return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc)); + } - Whitespace(); - DMASTExpression? expr3 = Expression(); - if (expr3 == null) { - if (Current().Type != TokenType.DM_RightParenthesis) { - Emit(WarningCode.BadExpression, "Expected 3nd expression in for"); - } + if (Check(TokenType.DM_RightParenthesis)) { + return new DMASTProcStatementFor(loc, expr1, expr2, null, dmTypes, GetForBody(loc)); + } - expr3 = new DMASTConstantNull(loc); + Whitespace(); + DMASTExpression? expr3 = Expression(); + if (expr3 == null) { + if (Current().Type != TokenType.DM_RightParenthesis) { + Emit(WarningCode.BadExpression, "Expected 3nd expression in for"); } - Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 3"); - return new DMASTProcStatementFor(loc, expr1, expr2, expr3, dmTypes, GetForBody(loc)); + expr3 = new DMASTConstantNull(loc); } - return null; + Consume(TokenType.DM_RightParenthesis, "Expected ')' in for after expression 3"); + return new DMASTProcStatementFor(loc, expr1, expr2, expr3, dmTypes, GetForBody(loc)); DMASTProcBlockInner GetForBody(Location forLocation) { Whitespace(); @@ -1279,7 +1254,7 @@ DMASTProcBlockInner GetForBody(Location forLocation) { } else { statement = ProcStatement(); if (statement == null) { - DMCompiler.Emit(WarningCode.MissingBody, forLocation, "Expected body or statement"); + Compiler.Emit(WarningCode.MissingBody, forLocation, "Expected body or statement"); statement = new DMASTInvalidProcStatement(loc); } } @@ -1291,96 +1266,87 @@ DMASTProcBlockInner GetForBody(Location forLocation) { } } - private DMASTProcStatement? While() { + private DMASTProcStatement While() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_While)) { - Whitespace(); - Consume(TokenType.DM_LeftParenthesis, "Expected '('"); - Whitespace(); - DMASTExpression? conditional = Expression(); - RequireExpression(ref conditional, "Expected a condition"); - ConsumeRightParenthesis(); - Check(TokenType.DM_Semicolon); - Whitespace(); - DMASTProcBlockInner? body = ProcBlock(); - - if (body == null) { - DMASTProcStatement? statement = ProcStatement(); + Whitespace(); + Consume(TokenType.DM_LeftParenthesis, "Expected '('"); + Whitespace(); + DMASTExpression? conditional = Expression(); + RequireExpression(ref conditional, "Expected a condition"); + ConsumeRightParenthesis(); + Check(TokenType.DM_Semicolon); + Whitespace(); + DMASTProcBlockInner? body = ProcBlock(); - //Loops without a body are valid DM - statement ??= new DMASTProcStatementContinue(loc); + if (body == null) { + DMASTProcStatement? statement = ProcStatement(); - body = new DMASTProcBlockInner(loc, statement); - } + //Loops without a body are valid DM + statement ??= new DMASTProcStatementContinue(loc); - if (conditional is DMASTConstantInteger integer && integer.Value != 0) { - return new DMASTProcStatementInfLoop(loc, body); - } + body = new DMASTProcBlockInner(loc, statement); + } - return new DMASTProcStatementWhile(loc, conditional, body); + if (conditional is DMASTConstantInteger integer && integer.Value != 0) { + return new DMASTProcStatementInfLoop(loc, body); } - return null; + return new DMASTProcStatementWhile(loc, conditional, body); } - private DMASTProcStatementDoWhile? DoWhile() { + private DMASTProcStatementDoWhile DoWhile() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Do)) { - Whitespace(); - DMASTProcBlockInner? body = ProcBlock(); - - if (body == null) { - DMASTProcStatement? statement = ProcStatement(); - if (statement is null) { // This is consistently fatal in BYOND - Emit(WarningCode.MissingBody, "Expected statement - do-while requires a non-empty block"); - //For the sake of argument, add a statement (avoids repetitive warning emissions down the line :^) ) - statement = new DMASTInvalidProcStatement(loc); - } + Whitespace(); + DMASTProcBlockInner? body = ProcBlock(); - body = new DMASTProcBlockInner(loc, new[] { statement }, null); + if (body == null) { + DMASTProcStatement? statement = ProcStatement(); + if (statement is null) { // This is consistently fatal in BYOND + Emit(WarningCode.MissingBody, "Expected statement - do-while requires a non-empty block"); + //For the sake of argument, add a statement (avoids repetitive warning emissions down the line :^) ) + statement = new DMASTInvalidProcStatement(loc); } - Newline(); - Whitespace(); - Consume(TokenType.DM_While, "Expected 'while'"); - Whitespace(); - Consume(TokenType.DM_LeftParenthesis, "Expected '('"); - Whitespace(); - DMASTExpression? conditional = Expression(); - RequireExpression(ref conditional, "Expected a condition"); - ConsumeRightParenthesis(); - Whitespace(); - - return new DMASTProcStatementDoWhile(loc, conditional, body); + body = new DMASTProcBlockInner(loc, new[] { statement }, null); } - return null; + Newline(); + Whitespace(); + Consume(TokenType.DM_While, "Expected 'while'"); + Whitespace(); + Consume(TokenType.DM_LeftParenthesis, "Expected '('"); + Whitespace(); + DMASTExpression? conditional = Expression(); + RequireExpression(ref conditional, "Expected a condition"); + ConsumeRightParenthesis(); + Whitespace(); + + return new DMASTProcStatementDoWhile(loc, conditional, body); } - private DMASTProcStatementSwitch? Switch() { + private DMASTProcStatementSwitch Switch() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Switch)) { - Whitespace(); - Consume(TokenType.DM_LeftParenthesis, "Expected '('"); - Whitespace(); - DMASTExpression? value = Expression(); - RequireExpression(ref value, "Switch statement is missing a value"); - ConsumeRightParenthesis(); - Whitespace(); - - DMASTProcStatementSwitch.SwitchCase[]? switchCases = SwitchCases(); - if (switchCases == null) { - switchCases = []; - Emit(WarningCode.MissingBody, "Expected switch cases"); - } + Whitespace(); + Consume(TokenType.DM_LeftParenthesis, "Expected '('"); + Whitespace(); + DMASTExpression? value = Expression(); + RequireExpression(ref value, "Switch statement is missing a value"); + ConsumeRightParenthesis(); + Whitespace(); - return new DMASTProcStatementSwitch(loc, value, switchCases); + DMASTProcStatementSwitch.SwitchCase[]? switchCases = SwitchCases(); + if (switchCases == null) { + switchCases = []; + Emit(WarningCode.MissingBody, "Expected switch cases"); } - return null; + return new DMASTProcStatementSwitch(loc, value, switchCases); } private DMASTProcStatementSwitch.SwitchCase[]? SwitchCases() { @@ -1450,7 +1416,7 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() { DMASTExpression? expression = Expression(); if (expression == null) { if (expressions.Count == 0) - DMCompiler.Emit(WarningCode.BadExpression, Current().Location, "Expected an expression"); + Compiler.Emit(WarningCode.BadExpression, Current().Location, "Expected an expression"); break; } @@ -1460,7 +1426,7 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() { var loc = Current().Location; DMASTExpression? rangeEnd = Expression(); if (rangeEnd == null) { - DMCompiler.Emit(WarningCode.BadExpression, loc, "Expected an upper limit"); + Compiler.Emit(WarningCode.BadExpression, loc, "Expected an upper limit"); rangeEnd = new DMASTConstantNull(loc); // Fallback to null } @@ -1491,7 +1457,7 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() { if (Current().Type == TokenType.DM_If) { //From now on, all if/elseif/else are actually part of this if's chain, not the switch's. //Ambiguous, but that is parity behaviour. Ergo, the following emission. - DMCompiler.Emit(WarningCode.SuspiciousSwitchCase, loc, + Compiler.Emit(WarningCode.SuspiciousSwitchCase, loc, "Expected \"if\" or \"else\" - \"else if\" is ambiguous as a switch case and may cause unintended flow"); } @@ -1511,63 +1477,57 @@ private DMASTProcStatementSwitch.SwitchCase[] SwitchInner() { return null; } - private DMASTProcStatementTryCatch? TryCatch() { + private DMASTProcStatementTryCatch TryCatch() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Try)) { - Whitespace(); - - DMASTProcBlockInner? tryBody = ProcBlock(); - if (tryBody == null) { - DMASTProcStatement? statement = ProcStatement(); - if (statement == null) { - statement = new DMASTInvalidProcStatement(loc); - Emit(WarningCode.MissingBody, "Expected body or statement"); - } + Whitespace(); - tryBody = new DMASTProcBlockInner(loc,statement); + DMASTProcBlockInner? tryBody = ProcBlock(); + if (tryBody == null) { + DMASTProcStatement? statement = ProcStatement(); + if (statement == null) { + statement = new DMASTInvalidProcStatement(loc); + Emit(WarningCode.MissingBody, "Expected body or statement"); } - Newline(); - Whitespace(); - Consume(TokenType.DM_Catch, "Expected catch"); - Whitespace(); + tryBody = new DMASTProcBlockInner(loc,statement); + } - // catch(var/exception/E) - DMASTProcStatement? parameter = null; - if (Check(TokenType.DM_LeftParenthesis)) { - BracketWhitespace(); - parameter = ProcVarDeclaration(allowMultiple: false); - BracketWhitespace(); - ConsumeRightParenthesis(); - Whitespace(); - } + Newline(); + Whitespace(); + Consume(TokenType.DM_Catch, "Expected catch"); + Whitespace(); - DMASTProcBlockInner? catchBody = ProcBlock(); - if (catchBody == null) { - DMASTProcStatement? statement = ProcStatement(); + // catch(var/exception/E) + DMASTProcStatement? parameter = null; + if (Check(TokenType.DM_LeftParenthesis)) { + BracketWhitespace(); + parameter = ProcVarDeclaration(allowMultiple: false); + BracketWhitespace(); + ConsumeRightParenthesis(); + Whitespace(); + } - if (statement != null) catchBody = new DMASTProcBlockInner(loc, statement); - } + DMASTProcBlockInner? catchBody = ProcBlock(); + if (catchBody == null) { + DMASTProcStatement? statement = ProcStatement(); - return new DMASTProcStatementTryCatch(loc, tryBody, catchBody, parameter); + if (statement != null) catchBody = new DMASTProcBlockInner(loc, statement); } - return null; + return new DMASTProcStatementTryCatch(loc, tryBody, catchBody, parameter); } - private DMASTProcStatementThrow? Throw() { + private DMASTProcStatementThrow Throw() { var loc = Current().Location; + Advance(); - if (Check(TokenType.DM_Throw)) { - Whitespace(); - DMASTExpression? value = Expression(); - RequireExpression(ref value, "Throw statement must have a value"); + Whitespace(); + DMASTExpression? value = Expression(); + RequireExpression(ref value, "Throw statement must have a value"); - return new DMASTProcStatementThrow(loc, value); - } else { - return null; - } + return new DMASTProcStatementThrow(loc, value); } private DMASTProcStatementLabel Label(DMASTIdentifier expression) { @@ -1742,9 +1702,9 @@ private List DefinitionParameters(out bool wasIndeterm } var type = AsComplexTypes(); - var dmType = DMObjectTree.GetDMObject(path.Path, false); - if (type is {Type: not DMValueType.Anything } && (value is null or DMASTConstantNull) && (dmType?.IsSubtypeOf(DreamPath.Datum) ?? false)) { - DMCompiler.Emit(WarningCode.ImplicitNullType, loc, $"Variable \"{path.Path}\" is null but not a subtype of atom nor explicitly typed as nullable, append \"|null\" to \"as\". It will implicitly be treated as nullable."); + Compiler.DMObjectTree.TryGetDMObject(path.Path, out var dmType); + if (type is { Type: not DMValueType.Anything } && (value is null or DMASTConstantNull) && (dmType?.IsSubtypeOf(DreamPath.Datum) ?? false)) { + Compiler.Emit(WarningCode.ImplicitNullType, loc, $"Variable \"{path.Path}\" is null but not a subtype of atom nor explicitly typed as nullable, append \"|null\" to \"as\". It will implicitly be treated as nullable."); type |= DMValueType.Null; } @@ -1787,10 +1747,10 @@ private void ExpressionTo(out DMASTExpression endRange, out DMASTExpression? ste } private DMASTExpression? ExpressionIn() { + var loc = Current().Location; // Don't check this inside, as Check() will advance and point at next token instead DMASTExpression? value = ExpressionAssign(); while (value != null && Check(TokenType.DM_In)) { - var loc = Current().Location; Whitespace(); DMASTExpression? list = ExpressionAssign(); @@ -2191,7 +2151,7 @@ private void ExpressionTo(out DMASTExpression endRange, out DMASTExpression? ste if (inner is null) { inner = new DMASTVoid(loc); } else { - inner = new DMASTExpressionWrapped(inner.Location, inner); + inner = new DMASTExpressionWrapped(loc, inner); } return inner; @@ -2220,7 +2180,7 @@ private void ExpressionTo(out DMASTExpression endRange, out DMASTExpression? ste //TODO actual modified type support if (Check(TokenType.DM_LeftCurlyBracket)) { - DMCompiler.UnimplementedWarning(path.Location, "Modified types are currently not supported and modified values will be ignored."); + Compiler.UnimplementedWarning(path.Location, "Modified types are currently not supported and modified values will be ignored."); BracketWhitespace(); Check(TokenType.DM_Indent); // The body could be indented. We ignore that. TODO: Better braced block parsing @@ -2375,14 +2335,19 @@ private void BracketWhitespace() { DMASTDereference.Operation operation; switch (token.Type) { + case TokenType.DM_Colon: + Compiler.Emit(WarningCode.RuntimeSearchOperator, token.Location, "Runtime search operator ':' should be avoided; prefer typecasting and using '.' instead"); + goto case TokenType.DM_QuestionPeriod; + case TokenType.DM_QuestionColon: + Compiler.Emit(WarningCode.RuntimeSearchOperator, token.Location, "Runtime search operator '?:' should be avoided; prefer typecasting and using '?.' instead"); + goto case TokenType.DM_QuestionPeriod; case TokenType.DM_Period: case TokenType.DM_QuestionPeriod: - case TokenType.DM_Colon: - case TokenType.DM_QuestionColon: { + { var identifier = Identifier(); if (identifier == null) { - DMCompiler.Emit(WarningCode.BadToken, token.Location, "Identifier expected"); + Compiler.Emit(WarningCode.BadToken, token.Location, "Identifier expected"); return new DMASTConstantNull(token.Location); } @@ -2414,7 +2379,7 @@ private void BracketWhitespace() { ConsumeRightBracket(); if (index == null) { - DMCompiler.Emit(WarningCode.BadToken, token.Location, "Expression expected"); + Compiler.Emit(WarningCode.BadToken, token.Location, "Expression expected"); return new DMASTConstantNull(token.Location); } @@ -2451,7 +2416,7 @@ private void BracketWhitespace() { break; case DMASTDereference.IndexOperation: - DMCompiler.Emit(WarningCode.BadToken, token.Location, "Attempt to call an invalid l-value"); + Compiler.Emit(WarningCode.BadToken, token.Location, "Attempt to call an invalid l-value"); return new DMASTConstantNull(token.Location); default: @@ -2735,7 +2700,7 @@ private void BracketWhitespace() { if (path == null) path = pathType; else - DMCompiler.Emit(WarningCode.BadToken, CurrentLoc, + Compiler.Emit(WarningCode.BadToken, CurrentLoc, $"Only one type path can be used, ignoring {pathType}"); } @@ -2767,13 +2732,13 @@ private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) { path = Path()?.Path; if (allowPath) { if (path is null) { - DMCompiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type or path"); + Compiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type or path"); } return DMValueType.Path; } - DMCompiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type"); + Compiler.Emit(WarningCode.BadToken, typeToken.Location, "Expected value type"); return 0; } @@ -2796,6 +2761,7 @@ private DMValueType SingleAsType(out DreamPath? path, bool allowPath = false) { case "path": return DMValueType.Path; case "opendream_unimplemented": return DMValueType.Unimplemented; case "opendream_compiletimereadonly": return DMValueType.CompiletimeReadonly; + case "opendream_noconstfold": return DMValueType.NoConstFold; default: Emit(WarningCode.BadToken, typeToken.Location, $"Invalid value type '{typeToken.Text}'"); return 0; diff --git a/DMCompiler/Compiler/DM/DMParserHelper.cs b/DMCompiler/Compiler/DM/DMParserHelper.cs index ca202a2770..38799187f7 100644 --- a/DMCompiler/Compiler/DM/DMParserHelper.cs +++ b/DMCompiler/Compiler/DM/DMParserHelper.cs @@ -6,7 +6,7 @@ namespace DMCompiler.Compiler.DM; -public partial class DMParser { +internal partial class DMParser { /// /// If the expression is null, emit an error and set it to a new /// @@ -44,7 +44,7 @@ protected void LocateNextTopLevel() { } if (Current().Type == TokenType.EndOfFile) break; - } while (((DMLexer)_lexer).CurrentIndentation() != 0); + } while (((DMLexer)Lexer).CurrentIndentation() != 0); Delimiter(); } @@ -53,7 +53,7 @@ private void ConsumeRightParenthesis() { // A missing right parenthesis has to subtract 1 from the lexer's bracket nesting counter // To keep indentation working correctly if (!Check(TokenType.DM_RightParenthesis)) { - ((DMLexer)_lexer).BracketNesting--; + ((DMLexer)Lexer).BracketNesting--; Emit(WarningCode.BadToken, "Expected ')'"); } } @@ -61,7 +61,7 @@ private void ConsumeRightParenthesis() { private void ConsumeRightBracket() { // Similar to ConsumeRightParenthesis() if (!Check(TokenType.DM_RightBracket)) { - ((DMLexer)_lexer).BracketNesting--; + ((DMLexer)Lexer).BracketNesting--; Emit(WarningCode.BadToken, "Expected ']'"); } } @@ -70,12 +70,12 @@ private void ConsumeRightBracket() { /// if error occurs. private bool CheckInterpolation(Location loc, bool hasSeenNonRefInterpolation, List? interpolationValues, string mack) { if (interpolationValues == null || interpolationValues.Count == 0) { - DMCompiler.Emit(WarningCode.MissingInterpolatedExpression, loc, $"Macro \"\\{mack}\" requires preceding interpolated expression"); + Compiler.Emit(WarningCode.MissingInterpolatedExpression, loc, $"Macro \"\\{mack}\" requires preceding interpolated expression"); return true; } if(!hasSeenNonRefInterpolation) { // More elaborate error for a more elaborate situation - DMCompiler.Emit(WarningCode.MissingInterpolatedExpression, loc, $"Macro \"\\{mack}\" requires preceding interpolated expression that is not a reference"); + Compiler.Emit(WarningCode.MissingInterpolatedExpression, loc, $"Macro \"\\{mack}\" requires preceding interpolated expression that is not a reference"); return true; } @@ -339,7 +339,7 @@ private DMASTExpression ExpressionFromString() { switch (currentToken.Type) { case TokenType.DM_ConstantString: // Constant singular piece of string, return here if (usedPrefixMacro != null) // FIXME: \the should not compiletime here, instead becoming a tab character followed by "he", when in parity mode - DMCompiler.Emit(WarningCode.MissingInterpolatedExpression, tokenLoc, + Compiler.Emit(WarningCode.MissingInterpolatedExpression, tokenLoc, $"Macro \"\\{usedPrefixMacro}\" requires interpolated expression"); return new DMASTConstantString(currentToken.Location, stringBuilder.ToString()); @@ -353,12 +353,12 @@ private DMASTExpression ExpressionFromString() { } else { var interpolatedExpression = Expression(); if (interpolatedExpression == null) - DMCompiler.Emit(WarningCode.MissingExpression, Current().Location, + Compiler.Emit(WarningCode.MissingExpression, Current().Location, "Expected an embedded expression"); // The next token should be the next piece of the string, error if not if (Current().Type is not TokenType.DM_StringMiddle and not TokenType.DM_StringEnd) { - DMCompiler.Emit(WarningCode.BadExpression, Current().Location, + Compiler.Emit(WarningCode.BadExpression, Current().Location, "Expected end of the embedded expression"); while (Current().Type is not TokenType.DM_StringMiddle and not TokenType.DM_StringEnd @@ -375,7 +375,7 @@ private DMASTExpression ExpressionFromString() { break; case TokenType.DM_StringEnd: // End of a string with interpolated values, return here if(currentInterpolationType != StringFormatEncoder.InterpolationDefault) { // this implies a prefix tried to modify a [] that never ended up existing after it - DMCompiler.Emit(WarningCode.MissingInterpolatedExpression, tokenLoc, + Compiler.Emit(WarningCode.MissingInterpolatedExpression, tokenLoc, $"Macro \"\\{usedPrefixMacro}\" must precede an interpolated expression"); } diff --git a/DMCompiler/Compiler/DMM/DMMParser.cs b/DMCompiler/Compiler/DMM/DMMParser.cs index 1995b28221..bf089252ed 100644 --- a/DMCompiler/Compiler/DMM/DMMParser.cs +++ b/DMCompiler/Compiler/DMM/DMMParser.cs @@ -1,11 +1,11 @@ -using DMCompiler.DM; -using DMCompiler.Compiler.DM; +using DMCompiler.Compiler.DM; using DMCompiler.Compiler.DM.AST; +using DMCompiler.DM.Builders; using DMCompiler.Json; namespace DMCompiler.Compiler.DMM; -internal sealed class DMMParser(DMLexer lexer, int zOffset) : DMParser(lexer) { +internal sealed class DMMParser(DMCompiler compiler, DMLexer lexer, int zOffset) : DMParser(compiler, lexer) { private int _cellNameLength = -1; private readonly HashSet _skippedTypes = new(); @@ -55,8 +55,7 @@ public DreamMapJson ParseMap() { CellDefinitionJson cellDefinition = new CellDefinitionJson(currentToken.ValueAsString()); DMASTPath? objectType = Path(); while (objectType != null) { - var type = DMObjectTree.GetDMObject(objectType.Path, createIfNonexistent: false); - if (type == null && _skippedTypes.Add(objectType.Path)) { + if (!Compiler.DMObjectTree.TryGetDMObject(objectType.Path, out var type) && _skippedTypes.Add(objectType.Path)) { Warning($"Skipping type '{objectType.Path}'"); } @@ -71,12 +70,17 @@ public DreamMapJson ParseMap() { break; } - if (!varOverride.ObjectPath.Equals(DreamPath.Root)) DMCompiler.ForcedError(statement.Location, $"Invalid var name '{varOverride.VarName}' in DMM on type {objectType.Path}"); - DMExpression value = DMExpression.Create(DMObjectTree.GetDMObject(objectType.Path, false), null, varOverride.Value); - if (!value.TryAsJsonRepresentation(out var valueJson)) DMCompiler.ForcedError(statement.Location, $"Failed to serialize value to json ({value})"); + if (!varOverride.ObjectPath.Equals(DreamPath.Root)) + Compiler.ForcedError(statement.Location, $"Invalid var name '{varOverride.VarName}' in DMM on type {objectType.Path}"); + + Compiler.DMObjectTree.TryGetDMObject(objectType.Path, out var dmObject); + var exprBuilder = new DMExpressionBuilder(new(Compiler, dmObject, null)); + var value = exprBuilder.Create(varOverride.Value); + if (!value.TryAsJsonRepresentation(Compiler, out var valueJson)) + Compiler.ForcedError(statement.Location, $"Failed to serialize value to json ({value})"); if(!mapObject.AddVarOverride(varOverride.VarName, valueJson)) { - DMCompiler.ForcedWarning(statement.Location, $"Duplicate var override '{varOverride.VarName}' in DMM on type {objectType.Path}"); + Compiler.ForcedWarning(statement.Location, $"Duplicate var override '{varOverride.VarName}' in DMM on type {objectType.Path}"); } CurrentPath = DreamPath.Root; diff --git a/DMCompiler/Compiler/DMPreprocessor/DMMacro.cs b/DMCompiler/Compiler/DMPreprocessor/DMMacro.cs index 2c30753bfa..71ded24456 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMMacro.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMMacro.cs @@ -46,12 +46,13 @@ public bool HasParameters() { /// /// Takes given parameters and creates a list of tokens representing the expanded macro /// + /// The DMCompiler compiling this program /// The identifier being replaced with this macro /// Parameters for macro expansion. Null if none given. /// A list of tokens replacing the identifier /// Thrown if no parameters were given but are required // TODO: Convert this to an IEnumerator? Could cut down on allocations. - public virtual List? Expand(Token replacing, List>? parameters) { + public virtual List? Expand(DMCompiler compiler, Token replacing, List>? parameters) { if (_tokens == null) return null; @@ -138,7 +139,7 @@ token.Type is TokenType.DM_Preproc_TokenConcat or TokenType.DM_Preproc_Parameter // __LINE__ internal sealed class DMMacroLine() : DMMacro(null, null) { - public override List Expand(Token replacing, List>? parameters) { + public override List Expand(DMCompiler compiler, Token replacing, List>? parameters) { var line = replacing.Location.Line; if (line == null) throw new ArgumentException($"Token {replacing} does not have a line number", nameof(replacing)); @@ -151,7 +152,7 @@ public override List Expand(Token replacing, List>? parameter // __FILE__ internal sealed class DMMacroFile() : DMMacro(null, null) { - public override List Expand(Token replacing, List>? parameters) { + public override List Expand(DMCompiler compiler, Token replacing, List>? parameters) { string path = replacing.Location.SourceFile.Replace(@"\", @"\\"); //Escape any backwards slashes return [ @@ -162,18 +163,18 @@ public override List Expand(Token replacing, List>? parameter // DM_VERSION internal sealed class DMMacroVersion() : DMMacro(null, null) { - public override List Expand(Token replacing, List>? parameters) { + public override List Expand(DMCompiler compiler, Token replacing, List>? parameters) { return [ - new Token(TokenType.DM_Preproc_Number, DMCompiler.Settings.DMVersion, replacing.Location, null) + new Token(TokenType.DM_Preproc_Number, compiler.Settings.DMVersion, replacing.Location, null) ]; } } // DM_BUILD internal sealed class DMMacroBuild() : DMMacro(null, null) { - public override List Expand(Token replacing, List>? parameters) { + public override List Expand(DMCompiler compiler, Token replacing, List>? parameters) { return [ - new Token(TokenType.DM_Preproc_Number, DMCompiler.Settings.DMBuild, replacing.Location, null) + new Token(TokenType.DM_Preproc_Number, compiler.Settings.DMBuild, replacing.Location, null) ]; } } diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs index c910000a15..de4ad91b44 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessor.cs @@ -10,7 +10,8 @@ namespace DMCompiler.Compiler.DMPreprocessor; /// The master class for handling DM preprocessing. /// This is an , and is usually accessed via its output in a for-loop. /// -public sealed class DMPreprocessor(bool enableDirectives) : IEnumerable { +internal sealed class DMPreprocessor(DMCompiler compiler, bool enableDirectives) : IEnumerable { + private readonly DMPreprocessorParser _dmPreprocessorParser = new(compiler); public readonly List IncludedMaps = new(8); public string? IncludedInterface; @@ -18,7 +19,7 @@ public sealed class DMPreprocessor(bool enableDirectives) : IEnumerable { private readonly Stack _lexerStack = new(8); // Capacity Note: TG peaks at 4 at time of writing private readonly Stack _bufferedWhitespace = new(); - private bool _currentLineContainsNonWhitespace = false; + private bool _currentLineContainsNonWhitespace; private bool _canUseDirective = true; private readonly HashSet _includedFiles = new(5120); // Capacity Note: TG peaks at 4860 at time of writing private readonly Stack _unprocessedTokens = new(8192); // Capacity Note: TG peaks at 6802 at time of writing @@ -29,12 +30,14 @@ public sealed class DMPreprocessor(bool enableDirectives) : IEnumerable { { "DM_VERSION", new DMMacroVersion() }, { "DM_BUILD", new DMMacroBuild() } }; + /// /// This stores previous evaluations of if-directives that have yet to find their #endif.
/// We do this so that we can A.) Detect whether an #else or #endif is valid and B.) Remember what to do when we find that #else. /// A null value indicates the last directive found was an #else that's waiting for an #endif. ///
private readonly Stack _lastIfEvaluations = new(16); + private Location _lastSeenIf = Location.Unknown; // used by the errors emitted for when the above var isn't empty at exit public IEnumerator GetEnumerator() { @@ -83,6 +86,7 @@ public IEnumerator GetEnumerator() { if (!_currentLineContainsNonWhitespace) { _bufferedWhitespace.Clear(); } + HandleIncludeDirective(token); break; case TokenType.DM_Preproc_Define: @@ -106,7 +110,7 @@ public IEnumerator GetEnumerator() { break; case TokenType.DM_Preproc_Else: if (!_lastIfEvaluations.TryPop(out bool? wasTruthy) || wasTruthy is null) - DMCompiler.Emit(WarningCode.BadDirective, token.Location, "Unexpected #else"); + compiler.Emit(WarningCode.BadDirective, token.Location, "Unexpected #else"); if (wasTruthy.Value) SkipIfBody(true); else @@ -121,7 +125,7 @@ public IEnumerator GetEnumerator() { break; case TokenType.DM_Preproc_EndIf: if (!_lastIfEvaluations.TryPop(out _)) - DMCompiler.Emit(WarningCode.BadDirective, token.Location, "Unexpected #endif"); + compiler.Emit(WarningCode.BadDirective, token.Location, "Unexpected #endif"); break; case TokenType.DM_Preproc_Identifier: { if (TryMacro(token)) { @@ -158,18 +162,19 @@ public IEnumerator GetEnumerator() { } case TokenType.Error: - DMCompiler.Emit(WarningCode.ErrorDirective, token.Location, token.ValueAsString()); + compiler.Emit(WarningCode.ErrorDirective, token.Location, token.ValueAsString()); break; default: - DMCompiler.Emit(WarningCode.BadToken, token.Location, + compiler.Emit(WarningCode.BadToken, token.Location, $"Invalid token encountered while preprocessing: {token.PrintableText} ({token.Type})"); break; } } + if(_lastIfEvaluations.Any()) - DMCompiler.Emit(WarningCode.BadDirective, _lastSeenIf, $"Missing {_lastIfEvaluations.Count} #endif directive{(_lastIfEvaluations.Count != 1 ? 's' : "")}"); - DMCompiler.CheckAllPragmasWereSet(); + compiler.Emit(WarningCode.BadDirective, _lastSeenIf, $"Missing {_lastIfEvaluations.Count} #endif directive{(_lastIfEvaluations.Count != 1 ? 's' : "")}"); + compiler.CheckAllPragmasWereSet(); } IEnumerator IEnumerable.GetEnumerator() { @@ -177,7 +182,7 @@ IEnumerator IEnumerable.GetEnumerator() { } public void DefineMacro(string key, string value) { - var lexer = new DMPreprocessorLexer(null, "", value); + var lexer = new DMPreprocessorLexer(compiler, null, "", value); var list = new List(); while (lexer.NextToken() is { Type: not TokenType.EndOfFile } token) { @@ -189,21 +194,21 @@ public void DefineMacro(string key, string value) { // NB: Pushes files to a stack, so call in reverse order if you are // including multiple files. - public void IncludeFile(string includeDir, string file, Location? includedFrom = null) { + public void IncludeFile(string includeDir, string file, bool isDMStandard, Location? includedFrom = null) { string filePath = Path.Combine(includeDir, file); filePath = filePath.Replace('\\', Path.DirectorySeparatorChar); if (_includedFiles.Contains(filePath)) { - DMCompiler.Emit(WarningCode.FileAlreadyIncluded, includedFrom ?? Location.Internal, $"File \"{filePath}\" was already included"); + compiler.Emit(WarningCode.FileAlreadyIncluded, includedFrom ?? Location.Internal, $"File \"{filePath}\" was already included"); return; } if (!File.Exists(filePath)) { - DMCompiler.Emit(WarningCode.MissingIncludedFile, includedFrom ?? Location.Internal, $"Could not find included file \"{filePath}\""); + compiler.Emit(WarningCode.MissingIncludedFile, includedFrom ?? Location.Internal, $"Could not find included file \"{filePath}\""); return; } - DMCompiler.VerbosePrint($"Including {file}"); + compiler.VerbosePrint($"Including {file}"); _includedFiles.Add(filePath); switch (Path.GetExtension(filePath)) { @@ -214,11 +219,11 @@ public void IncludeFile(string includeDir, string file, Location? includedFrom = case ".dmf": if (IncludedInterface != null) { if(IncludedInterface == filePath) { - DMCompiler.Emit(WarningCode.FileAlreadyIncluded, includedFrom ?? Location.Internal, $"Interface \"{filePath}\" was already included"); + compiler.Emit(WarningCode.FileAlreadyIncluded, includedFrom ?? Location.Internal, $"Interface \"{filePath}\" was already included"); break; } - DMCompiler.Emit(WarningCode.InvalidInclusion, includedFrom ?? Location.Internal, $"Attempted to include a second interface file ({filePath}) while one was already included ({IncludedInterface})"); + compiler.Emit(WarningCode.InvalidInclusion, includedFrom ?? Location.Internal, $"Attempted to include a second interface file ({filePath}) while one was already included ({IncludedInterface})"); break; } @@ -226,28 +231,28 @@ public void IncludeFile(string includeDir, string file, Location? includedFrom = break; case ".dms": // Webclient interface file. Probably never gonna be supported. - DMCompiler.UnimplementedWarning(includedFrom ?? Location.Internal, "DMS files are not supported"); + compiler.UnimplementedWarning(includedFrom ?? Location.Internal, "DMS files are not supported"); break; default: - PreprocessFile(includeDir, file); + PreprocessFile(includeDir, file, isDMStandard); break; } } - public void PreprocessFile(string includeDir, string file) { + public void PreprocessFile(string includeDir, string file, bool isDMStandard) { file = file.Replace('\\', '/'); - _lexerStack.Push(new DMPreprocessorLexer(includeDir, file)); + _lexerStack.Push(new DMPreprocessorLexer(compiler, includeDir, file, isDMStandard)); } private bool VerifyDirectiveUsage(Token token) { if (!enableDirectives) { - DMCompiler.Emit(WarningCode.MisplacedDirective, token.Location, "Cannot use a preprocessor directive here"); + compiler.Emit(WarningCode.MisplacedDirective, token.Location, "Cannot use a preprocessor directive here"); return false; } if (!_canUseDirective) { - DMCompiler.Emit(WarningCode.MisplacedDirective, token.Location, "There can only be whitespace before a preprocessor directive"); + compiler.Emit(WarningCode.MisplacedDirective, token.Location, "There can only be whitespace before a preprocessor directive"); return false; } @@ -260,7 +265,7 @@ private void HandleIncludeDirective(Token includeToken) { Token includedFileToken = GetNextToken(true); if (includedFileToken.Type != TokenType.DM_Preproc_ConstantString) { - DMCompiler.Emit(WarningCode.InvalidInclusion, includeToken.Location, $"\"{includedFileToken.Text}\" is not a valid include path"); + compiler.Emit(WarningCode.InvalidInclusion, includeToken.Location, $"\"{includedFileToken.Text}\" is not a valid include path"); return; } @@ -268,7 +273,7 @@ private void HandleIncludeDirective(Token includeToken) { string file = Path.Combine(Path.GetDirectoryName(currentLexer.File.Replace('\\', Path.DirectorySeparatorChar)), includedFileToken.ValueAsString()); string directory = currentLexer.IncludeDirectory; - IncludeFile(directory, file, includedFrom: includeToken.Location); + IncludeFile(directory, file, includeToken.Location.InDMStandard, includedFrom: includeToken.Location); } private void HandleDefineDirective(Token defineToken) { @@ -277,7 +282,7 @@ private void HandleDefineDirective(Token defineToken) { Token defineIdentifier = GetNextToken(true); if (defineIdentifier.Type != TokenType.DM_Preproc_Identifier) { - DMCompiler.Emit(WarningCode.BadDirective, defineIdentifier.Location, "Unexpected token, identifier expected for #define directive"); + compiler.Emit(WarningCode.BadDirective, defineIdentifier.Location, "Unexpected token, identifier expected for #define directive"); GetLineOfTokens(); // consume what's on this line and leave return; } @@ -293,19 +298,19 @@ private void HandleDefineDirective(Token defineToken) { }; if (dirTokenValue is null) { - DMCompiler.Emit(WarningCode.BadDirective, dirToken.Location, $"\"{dirToken.Text}\" is not a valid directory"); + compiler.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); + compiler.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"); + compiler.Emit(WarningCode.SoftReservedKeyword, defineIdentifier.Location, "Reserved keyword 'defined' cannot be used as macro name"); } List parameters = null; @@ -323,13 +328,13 @@ private void HandleDefineDirective(Token defineToken) { case TokenType.DM_Preproc_Identifier: canConsumeComma = true; if (foundVariadic) { - DMCompiler.Emit(WarningCode.BadDirective, parameterToken.Location, $"Variadic argument '{parameters.Last()}' must be the last argument"); + compiler.Emit(WarningCode.BadDirective, parameterToken.Location, $"Variadic argument '{parameters.Last()}' must be the last argument"); foundVariadic = false; // Reduces error spam if there's several arguments after it continue; } if(Check(TokenType.DM_Preproc_Punctuator_Period)) { // Check for a variadic if (!Check(TokenType.DM_Preproc_Punctuator_Period) || !Check(TokenType.DM_Preproc_Punctuator_Period)) { - DMCompiler.Emit(WarningCode.BadDirective, parameterToken.Location, $"Invalid macro parameter, '{parameterToken.Text}...' expected"); + compiler.Emit(WarningCode.BadDirective, parameterToken.Location, $"Invalid macro parameter, '{parameterToken.Text}...' expected"); } parameters.Add($"{parameterToken.Text}..."); foundVariadic = true; @@ -341,11 +346,11 @@ private void HandleDefineDirective(Token defineToken) { continue; case TokenType.DM_Preproc_Punctuator_Period: // One of those "..." things, maybe? if (!Check(TokenType.DM_Preproc_Punctuator_Period) || !Check(TokenType.DM_Preproc_Punctuator_Period)) { - DMCompiler.Emit(WarningCode.BadDirective, parameterToken.Location, "Invalid macro parameter, '...' expected"); + compiler.Emit(WarningCode.BadDirective, parameterToken.Location, "Invalid macro parameter, '...' expected"); } canConsumeComma = true; if (foundVariadic) { // Placed here so we properly consume this bogus '...' parameter if need be - DMCompiler.Emit(WarningCode.BadDirective, parameterToken.Location, $"Variadic argument '{parameters.Last()}' must be the last argument"); + compiler.Emit(WarningCode.BadDirective, parameterToken.Location, $"Variadic argument '{parameters.Last()}' must be the last argument"); foundVariadic = false; // Reduces error spam if there's several arguments after it continue; } @@ -353,17 +358,17 @@ private void HandleDefineDirective(Token defineToken) { continue; case TokenType.DM_Preproc_Punctuator_Comma: if(!canConsumeComma) - DMCompiler.Emit(WarningCode.BadDirective, parameterToken.Location, "Unexpected ',' in macro parameter list"); + compiler.Emit(WarningCode.BadDirective, parameterToken.Location, "Unexpected ',' in macro parameter list"); canConsumeComma = false; continue; case TokenType.DM_Preproc_Punctuator_RightParenthesis: break; case TokenType.EndOfFile: - DMCompiler.Emit(WarningCode.BadDirective, macroToken.Location, "Missing ')' in macro definition"); // Location points to the left paren! + compiler.Emit(WarningCode.BadDirective, macroToken.Location, "Missing ')' in macro definition"); // Location points to the left paren! PushToken(parameterToken); break; default: - DMCompiler.Emit(WarningCode.BadDirective, parameterToken.Location, "Expected a macro parameter"); + compiler.Emit(WarningCode.BadDirective, parameterToken.Location, "Expected a macro parameter"); return; } break; // If the switch gets here, the loop ends. @@ -405,10 +410,10 @@ private void HandleUndefineDirective(Token undefToken) { Token defineIdentifier = GetNextToken(true); if (defineIdentifier.Type != TokenType.DM_Preproc_Identifier) { - DMCompiler.Emit(WarningCode.BadDirective, defineIdentifier.Location, "Invalid macro identifier"); + compiler.Emit(WarningCode.BadDirective, defineIdentifier.Location, "Invalid macro identifier"); return; } else if (!_defines.ContainsKey(defineIdentifier.Text)) { - DMCompiler.Emit(WarningCode.UndefineMissingDirective, defineIdentifier.Location, $"No macro named \"{defineIdentifier.PrintableText}\""); + compiler.Emit(WarningCode.UndefineMissingDirective, defineIdentifier.Location, $"No macro named \"{defineIdentifier.PrintableText}\""); return; } @@ -466,7 +471,7 @@ private bool TryMacro(Token token) { return false; } - List? expandedTokens = macro.Expand(token, parameters); + List? expandedTokens = macro.Expand(compiler, token, parameters); if (expandedTokens != null) { for (int i = expandedTokens.Count - 1; i >= 0; i--) { Token expandedToken = expandedTokens[i]; @@ -497,13 +502,13 @@ private void HandleIfDirective(Token ifToken) { var tokens = GetLineOfTokens(); if (!tokens.Any()) { // If empty - DMCompiler.Emit(WarningCode.BadDirective, ifToken.Location, "Expression expected for #if"); + compiler.Emit(WarningCode.BadDirective, ifToken.Location, "Expression expected for #if"); HandleDegenerateIf(); return; } - float? expr = DMPreprocessorParser.ExpressionFromTokens(tokens, _defines); + float? expr = _dmPreprocessorParser.ExpressionFromTokens(tokens, _defines); if(expr is null) { - DMCompiler.Emit(WarningCode.BadDirective, ifToken.Location, "Expression is invalid"); + compiler.Emit(WarningCode.BadDirective, ifToken.Location, "Expression is invalid"); HandleDegenerateIf(); return; } @@ -520,7 +525,7 @@ private void HandleIfDefDirective(Token ifDefToken) { Token define = GetNextToken(true); if (define.Type != TokenType.DM_Preproc_Identifier) { - DMCompiler.Emit(WarningCode.BadDirective, ifDefToken.Location, "Expected a define identifier"); + compiler.Emit(WarningCode.BadDirective, ifDefToken.Location, "Expected a define identifier"); HandleDegenerateIf(); return; } @@ -538,7 +543,7 @@ private void HandleIfNDefDirective(Token ifNDefToken) { Token define = GetNextToken(true); if (define.Type != TokenType.DM_Preproc_Identifier) { - DMCompiler.Emit(WarningCode.BadDirective, ifNDefToken.Location, "Expected a define identifier"); + compiler.Emit(WarningCode.BadDirective, ifNDefToken.Location, "Expected a define identifier"); HandleDegenerateIf(); return; } @@ -552,9 +557,9 @@ private void HandleIfNDefDirective(Token ifNDefToken) { private void HandleElifDirective(Token elifToken) { if (!_lastIfEvaluations.TryPeek(out bool? wasTruthy)) - DMCompiler.Emit(WarningCode.BadDirective, elifToken.Location, "Unexpected #elif"); + compiler.Emit(WarningCode.BadDirective, elifToken.Location, "Unexpected #elif"); if (wasTruthy is null) { - DMCompiler.Emit(WarningCode.BadDirective, elifToken.Location, "Directive #elif cannot appear after #else in its flow control"); + compiler.Emit(WarningCode.BadDirective, elifToken.Location, "Directive #elif cannot appear after #else in its flow control"); SkipIfBody(); } else if (wasTruthy.Value) SkipIfBody(); @@ -568,7 +573,7 @@ private void HandleErrorOrWarningDirective(Token token) { if (!VerifyDirectiveUsage(token)) return; - DMCompiler.Emit( + compiler.Emit( token.Type == TokenType.DM_Preproc_Error ? WarningCode.ErrorDirective : WarningCode.WarningDirective, token.Location, token.Text); } @@ -601,7 +606,7 @@ private void HandlePragmaDirective() { switch(warningNameToken.Type) { case TokenType.DM_Preproc_Identifier: { if (!Enum.TryParse(warningNameToken.Text, out warningCode)) { - DMCompiler.Emit(WarningCode.InvalidWarningCode, warningNameToken.Location, $"Warning '{warningNameToken.PrintableText}' does not exist"); + compiler.Emit(WarningCode.InvalidWarningCode, warningNameToken.Location, $"Warning '{warningNameToken.PrintableText}' does not exist"); GetLineOfTokens(); // consume what's on this line and leave return; } @@ -610,7 +615,7 @@ private void HandlePragmaDirective() { } case TokenType.DM_Preproc_Number: { if (!int.TryParse(warningNameToken.Text, out var intValue)) { - DMCompiler.Emit(WarningCode.InvalidWarningCode, warningNameToken.Location, $"Warning OD{warningNameToken.PrintableText} does not exist"); + compiler.Emit(WarningCode.InvalidWarningCode, warningNameToken.Location, $"Warning OD{warningNameToken.PrintableText} does not exist"); GetLineOfTokens(); return; } @@ -619,43 +624,43 @@ private void HandlePragmaDirective() { break; } default: { - DMCompiler.Emit(WarningCode.BadDirective, warningNameToken.Location, $"Invalid warning identifier '{warningNameToken.PrintableText}'"); + compiler.Emit(WarningCode.BadDirective, warningNameToken.Location, $"Invalid warning identifier '{warningNameToken.PrintableText}'"); GetLineOfTokens(); return; } } if((int)warningCode < 1000) { - DMCompiler.Emit(WarningCode.BadDirective, warningNameToken.Location, $"Warning OD{(int)warningCode:d4} cannot be set - it must always be an error"); + compiler.Emit(WarningCode.BadDirective, warningNameToken.Location, $"Warning OD{(int)warningCode:d4} cannot be set - it must always be an error"); GetLineOfTokens(); return; } Token warningTypeToken = GetNextToken(true); if (warningTypeToken.Type != TokenType.DM_Preproc_Identifier) { - DMCompiler.Emit(WarningCode.BadDirective, warningNameToken.Location, $"Warnings can only be set to disabled, notice, warning, or error"); + compiler.Emit(WarningCode.BadDirective, warningTypeToken.Location, "Warnings can only be set to disabled, notice, warning, or error"); return; } switch(warningTypeToken.Text.ToLower()) { case "disabled": case "disable": - DMCompiler.SetPragma(warningCode, ErrorLevel.Disabled); + compiler.SetPragma(warningCode, ErrorLevel.Disabled); break; case "notice": case "pedantic": case "info": - DMCompiler.SetPragma(warningCode, ErrorLevel.Notice); + compiler.SetPragma(warningCode, ErrorLevel.Notice); break; case "warning": case "warn": - DMCompiler.SetPragma(warningCode, ErrorLevel.Warning); + compiler.SetPragma(warningCode, ErrorLevel.Warning); break; case "error": case "err": - DMCompiler.SetPragma(warningCode, ErrorLevel.Error); + compiler.SetPragma(warningCode, ErrorLevel.Error); break; default: - DMCompiler.Emit(WarningCode.BadDirective, warningNameToken.Location, $"Warnings can only be set to disabled, notice, warning, or error"); + compiler.Emit(WarningCode.BadDirective, warningTypeToken.Location, "Warnings can only be set to disabled, notice, warning, or error"); return; } } @@ -714,7 +719,7 @@ private bool SkipIfBody(bool calledByElseDirective = false) { if (ifStack != 1) break; if (calledByElseDirective) - DMCompiler.Emit(WarningCode.BadDirective, token.Location, $"Unexpected {token.PrintableText} directive"); + compiler.Emit(WarningCode.BadDirective, token.Location, $"Unexpected {token.PrintableText} directive"); _unprocessedTokens.Push(token); // Push it back onto the stack so we can interpret this later return true; default: @@ -728,7 +733,7 @@ private bool SkipIfBody(bool calledByElseDirective = false) { return false; } } - DMCompiler.Emit(WarningCode.BadDirective, Location.Unknown, "Missing #endif directive"); + compiler.Emit(WarningCode.BadDirective, Location.Unknown, "Missing #endif directive"); return false; } @@ -784,7 +789,7 @@ private bool TryGetMacroParameters(out List>? parameters) { parameters.Add(currentParameter); if (parameterToken.Type != TokenType.DM_Preproc_Punctuator_RightParenthesis) { - DMCompiler.Emit(WarningCode.BadDirective, leftParenToken.Value.Location, "Missing ')' in macro call"); + compiler.Emit(WarningCode.BadDirective, leftParenToken.Value.Location, "Missing ')' in macro call"); return false; } diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs index ef4772131e..83a3a3a403 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorLexer.cs @@ -15,12 +15,16 @@ internal sealed class DMPreprocessorLexer { public readonly string IncludeDirectory; public readonly string File; + private readonly DMCompiler _compiler; private readonly StreamReader _source; + private readonly bool _isDMStandard; private char _current; private int _currentLine = 1, _currentColumn; + private int _previousLine = 1, _previousColumn; private readonly Queue _pendingTokenQueue = new(); // TODO: Possible to remove this? - public DMPreprocessorLexer(string includeDirectory, string file, string source) { + public DMPreprocessorLexer(DMCompiler compiler, string includeDirectory, string file, string source) { + _compiler = compiler; IncludeDirectory = includeDirectory; File = file; @@ -28,11 +32,13 @@ public DMPreprocessorLexer(string includeDirectory, string file, string source) Advance(); } - public DMPreprocessorLexer(string includeDirectory, string file) { + public DMPreprocessorLexer(DMCompiler compiler, string includeDirectory, string file, bool isDMStandard) { + _compiler = compiler; IncludeDirectory = includeDirectory; File = file; _source = new StreamReader(Path.Combine(includeDirectory, file), Encoding.UTF8); + _isDMStandard = isDMStandard; Advance(); } @@ -56,6 +62,9 @@ public Token NextToken(bool ignoreWhitespace = false) { char c = GetCurrent(); + _previousLine = _currentLine; + _previousColumn = _currentColumn; + switch (c) { case '\0': return CreateToken(TokenType.EndOfFile, c); @@ -362,7 +371,7 @@ public Token NextToken(bool ignoreWhitespace = false) { LexString(true) : CreateToken(TokenType.DM_Preproc_Punctuator, c); case '#': { - bool isConcat = (Advance() == '#'); + bool isConcat = Advance() == '#'; if (isConcat) Advance(); // Whitespace after '#' is ignored @@ -388,7 +397,7 @@ public Token NextToken(bool ignoreWhitespace = false) { string macroAttempt = text.ToLower(); if (TryMacroKeyword(macroAttempt, out var attemptKeyword)) { // if they mis-capitalized the keyword - DMCompiler.Emit(WarningCode.MiscapitalizedDirective, attemptKeyword.Value.Location, + _compiler.Emit(WarningCode.MiscapitalizedDirective, attemptKeyword.Value.Location, $"#{text} is not a valid macro keyword. Did you mean '#{macroAttempt}'?"); } @@ -439,7 +448,7 @@ public Token NextToken(bool ignoreWhitespace = false) { } Advance(); - return CreateToken(TokenType.Error, string.Empty, $"Unknown character: {c.ToString()}"); + return CreateToken(TokenType.Error, string.Empty, $"Unknown character: {c}"); } } } @@ -614,7 +623,7 @@ private bool HandleLineEnd() { goto case '\n'; case '\n': _currentLine++; - _currentColumn = 1; + _currentColumn = 0; // Because Advance will bump this to 1 and any position reads will happen next NextToken() call if (c == '\n') // This line could have ended with only \r Advance(); @@ -636,7 +645,7 @@ private char Advance() { if (value == -1) { _current = '\0'; - } else { + } else { _currentColumn++; _current = (char)value; } @@ -651,11 +660,11 @@ private bool AtEndOfSource() { [MethodImpl(MethodImplOptions.AggressiveInlining)] private Token CreateToken(TokenType type, string text, object? value = null) { - return new Token(type, text, new Location(File, _currentLine, _currentColumn), value); + return new Token(type, text, new Location(File, _previousLine, _previousColumn, _isDMStandard), value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private Token CreateToken(TokenType type, char text, object? value = null) { - return new Token(type, text.ToString(), new Location(File, _currentLine, _currentColumn), value); + return new Token(type, text.ToString(), new Location(File, _previousLine, _previousColumn, _isDMStandard), value); } } diff --git a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorParser.cs b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorParser.cs index f732dba537..5130902a19 100644 --- a/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorParser.cs +++ b/DMCompiler/Compiler/DMPreprocessor/DMPreprocessorParser.cs @@ -25,14 +25,14 @@ namespace DMCompiler.Compiler.DMPreprocessor; /// An extremely simple parser that acts on a sliver of tokens that have been DM-lexed for evaluation in a preprocessor directive,
/// held separate from DMParser because of slightly different behaviour, far simpler implementation, and () possible statelessness. /// -internal static class DMPreprocessorParser { - private static List? _tokens; - private static Dictionary? _defines; - private static int _tokenIndex; - private static readonly float DegenerateValue = 0.0f; +internal class DMPreprocessorParser(DMCompiler compiler) { + private List? _tokens; + private Dictionary? _defines; + private int _tokenIndex; + private readonly float DegenerateValue = 0.0f; /// A float, because that is the only possible thing a well-formed preproc expression can evaluate to. - public static float? ExpressionFromTokens(List input, Dictionary defines) { + public float? ExpressionFromTokens(List input, Dictionary defines) { _tokens = input; _defines = defines; var ret = Expression(); @@ -42,18 +42,18 @@ internal static class DMPreprocessorParser { return ret; } - private static void Advance() { + private void Advance() { ++_tokenIndex; } - private static Token Current() { + private Token Current() { if (_tokenIndex >= _tokens!.Count) return new Token(TokenType.EndOfFile, "\0", Location.Unknown, null); return _tokens[_tokenIndex]; } - private static bool Check(TokenType type) { + private bool Check(TokenType type) { if (Current().Type == type) { Advance(); return true; @@ -62,7 +62,7 @@ private static bool Check(TokenType type) { return false; } - private static bool Check(TokenType[] types) { + private bool Check(TokenType[] types) { foreach (TokenType type in types) { if (Current().Type == type) { Advance(); @@ -73,15 +73,15 @@ private static bool Check(TokenType[] types) { return false; } - private static void Error(string msg) { - DMCompiler.Emit(WarningCode.BadDirective, Current().Location, msg); + private void Error(string msg) { + compiler.Emit(WarningCode.BadDirective, Current().Location, msg); } - private static float? Expression() { + private float? Expression() { return ExpressionOr(); } - private static float? ExpressionOr() { + private float? ExpressionOr() { float? a = ExpressionAnd(); if (a is null) return a; @@ -101,7 +101,7 @@ private static void Error(string msg) { return a; } - private static float? ExpressionAnd() { + private float? ExpressionAnd() { float? a = ExpressionComparison(); if (a is null) return a; @@ -121,7 +121,7 @@ private static void Error(string msg) { return a; } - private static float? ExpressionComparison() { + private float? ExpressionComparison() { float? a = ExpressionComparisonLtGt(); if (a is null) return a; for (Token token = Current(); Check(DMParser.ComparisonTypes); token = Current()) { @@ -150,7 +150,7 @@ private static void Error(string msg) { return a; } - private static float? ExpressionComparisonLtGt() { + private float? ExpressionComparisonLtGt() { float? a = ExpressionAdditionSubtraction(); if (a is null) return a; for (Token token = Current(); Check(DMParser.LtGtComparisonTypes); token = Current()) { @@ -179,7 +179,7 @@ private static void Error(string msg) { return a; } - private static float? ExpressionAdditionSubtraction() { + private float? ExpressionAdditionSubtraction() { float? a = ExpressionMultiplicationDivisionModulus(); if (a is null) return a; for (Token token = Current(); Check(DMParser.PlusMinusTypes); token = Current()) { @@ -202,7 +202,7 @@ private static void Error(string msg) { return a; } - private static float? ExpressionMultiplicationDivisionModulus() { + private float? ExpressionMultiplicationDivisionModulus() { float? a = ExpressionPower(); if (a is null) return a; for (Token token = Current(); Check(DMParser.MulDivModTypes); token = Current()) { @@ -228,7 +228,7 @@ private static void Error(string msg) { return a; } - private static float? ExpressionPower() { + private float? ExpressionPower() { float? a = ExpressionUnary(); if (a is null) return a; @@ -245,7 +245,7 @@ private static void Error(string msg) { return a; } - private static float? ExpressionUnary() { + private float? ExpressionUnary() { if (Check(TokenType.DM_Exclamation)) { float? expression = ExpressionUnary(); if (expression == null) { @@ -259,7 +259,7 @@ private static void Error(string msg) { return ExpressionSign(); } - private static float? ExpressionSign() { + private float? ExpressionSign() { Token token = Current(); if (Check(DMParser.PlusMinusTypes)) { @@ -278,7 +278,7 @@ private static void Error(string msg) { return ExpressionPrimary(); } - private static float? ExpressionPrimary() { + private float? ExpressionPrimary() { Token token = Current(); switch (token.Type) { case TokenType.DM_LeftParenthesis: @@ -306,7 +306,7 @@ private static void Error(string msg) { Advance(); if (!Check(TokenType.DM_RightParenthesis)) { - DMCompiler.Emit(WarningCode.DefinedMissingParen, token.Location, + compiler.Emit(WarningCode.DefinedMissingParen, token.Location, "Expected ')' to end defined() expression"); //Electing to not return a degenerate value here since "defined(x" actually isn't an ambiguous grammar; we can figure out what they meant. } @@ -328,13 +328,13 @@ private static void Error(string msg) { Advance(); if (!Check(TokenType.DM_RightParenthesis)) { - DMCompiler.Emit(WarningCode.DefinedMissingParen, token.Location, + compiler.Emit(WarningCode.DefinedMissingParen, token.Location, "Expected ')' to end fexists() expression"); } var filePath = Path.GetRelativePath(".", fExistsInner.ValueAsString().Replace('\\', '/')); - var outputDir = Path.Combine(Path.GetDirectoryName(DMCompiler.Settings.Files?[0]) ?? "/", Path.GetDirectoryName(fExistsInner.Location.SourceFile) ?? "/"); + var outputDir = Path.Combine(Path.GetDirectoryName(compiler.Settings.Files?[0]) ?? "/", Path.GetDirectoryName(fExistsInner.Location.SourceFile) ?? "/"); if (string.IsNullOrEmpty(outputDir)) outputDir = "./"; @@ -350,7 +350,7 @@ private static void Error(string msg) { } } - private static float? Constant() { + private float? Constant() { Token constantToken = Current(); switch (constantToken.Type) { diff --git a/DMCompiler/Compiler/Lexer.cs b/DMCompiler/Compiler/Lexer.cs index ce5222595b..27b3560120 100644 --- a/DMCompiler/Compiler/Lexer.cs +++ b/DMCompiler/Compiler/Lexer.cs @@ -2,20 +2,33 @@ namespace DMCompiler.Compiler; -public class Lexer { +internal class Lexer { + /// + /// Location of token that'll be output by . If you skip through more + /// public Location CurrentLocation { get; protected set; } - public string SourceName { get; protected set; } - public IEnumerable Source { get; protected set; } - public bool AtEndOfSource { get; protected set; } = false; + /// + /// Location of a previous token. + /// + public Location PreviousLocation { get; private set; } + + public IEnumerable Source { get; } + public bool AtEndOfSource { get; private set; } protected Queue _pendingTokenQueue = new(); - private readonly IEnumerator _sourceEnumerator; - private SourceType _current; + private readonly IEnumerator _sourceEnumerator; + private TSourceType _current; - protected Lexer(string sourceName, IEnumerable source) { + /// + /// Given a stream of some type, allows to advance through it and create tokens + /// + /// Used to build the initial Location, access through + /// Source of input + /// Thrown if is null + protected Lexer(string sourceName, IEnumerable source) { CurrentLocation = new Location(sourceName, 1, 0); - SourceName = sourceName; + PreviousLocation = CurrentLocation; Source = source; if (source == null) throw new FileNotFoundException("Source file could not be read: " + sourceName); @@ -26,7 +39,7 @@ public Token GetNextToken() { if (_pendingTokenQueue.Count > 0) return _pendingTokenQueue.Dequeue(); - Token nextToken = ParseNextToken(); + var nextToken = ParseNextToken(); while (nextToken.Type == TokenType.Skip) nextToken = ParseNextToken(); if (_pendingTokenQueue.Count > 0) { @@ -41,19 +54,36 @@ protected virtual Token ParseNextToken() { return CreateToken(TokenType.Unknown, GetCurrent()?.ToString() ?? string.Empty); } + protected Token CreateToken(TokenType type, string text, Location location, object? value = null) { + var token = new Token(type, text, location, value); + return token; + } + + /// + /// Creates a new located at + /// + /// + /// If you have used more than once, the will be incorrect, + /// and you'll need to use + /// with a previously recorded + /// protected Token CreateToken(TokenType type, string text, object? value = null) { - return new Token(type, text, CurrentLocation, value); + return CreateToken(type, text, PreviousLocation, value); } + /// protected Token CreateToken(TokenType type, char text, object? value = null) { return CreateToken(type, char.ToString(text), value); } - protected virtual SourceType GetCurrent() { + protected virtual TSourceType GetCurrent() { return _current; } - protected virtual SourceType Advance() { + /// Call before CreateToken to make sure the location is correct + protected virtual TSourceType Advance() { + PreviousLocation = CurrentLocation; + if (_sourceEnumerator.MoveNext()) { _current = _sourceEnumerator.Current; } else { @@ -64,56 +94,9 @@ protected virtual SourceType Advance() { } } -public class TextLexer : Lexer { - protected string _source; - protected int _currentPosition = 0; - - public TextLexer(string sourceName, string source) : base(sourceName, source) { - _source = source; - - Advance(); - } - - protected override Token ParseNextToken() { - char c = GetCurrent(); - - Token token; - switch (c) { - case '\n': token = CreateToken(TokenType.Newline, c); Advance(); break; - case '\0': token = CreateToken(TokenType.EndOfFile, c); Advance(); break; - default: token = CreateToken(TokenType.Unknown, c); break; - } - - return token; - } - - protected override char GetCurrent() { - if (AtEndOfSource) return '\0'; - else return base.GetCurrent(); - } - - protected override char Advance() { - if (GetCurrent() == '\n') { - CurrentLocation = new Location( - CurrentLocation.SourceFile, - CurrentLocation.Line + 1, - 1 - ); - } else { - CurrentLocation = new Location( - CurrentLocation.SourceFile, - CurrentLocation.Line, - CurrentLocation.Column + 1 - ); - } - - _currentPosition++; - return base.Advance(); - } -} - -public class TokenLexer : Lexer { - public TokenLexer(string sourceName, IEnumerable source) : base(sourceName, source) { +internal class TokenLexer : Lexer { + /// + protected TokenLexer(string sourceName, IEnumerable source) : base(sourceName, source) { Advance(); } diff --git a/DMCompiler/Compiler/Parser.cs b/DMCompiler/Compiler/Parser.cs index e24de27e30..e740267f7d 100644 --- a/DMCompiler/Compiler/Parser.cs +++ b/DMCompiler/Compiler/Parser.cs @@ -1,12 +1,15 @@ namespace DMCompiler.Compiler; -public class Parser { - protected Lexer _lexer; +internal class Parser { + protected readonly Lexer Lexer; + protected readonly DMCompiler Compiler; + private Token _currentToken; private readonly Stack _tokenStack = new(1); - protected Parser(Lexer lexer) { - _lexer = lexer; + internal Parser(DMCompiler compiler, Lexer lexer) { + Compiler = compiler; + Lexer = lexer; Advance(); } @@ -22,7 +25,7 @@ protected virtual Token Advance() { if (_tokenStack.Count > 0) { _currentToken = _tokenStack.Pop(); } else { - _currentToken = _lexer.GetNextToken(); + _currentToken = Lexer.GetNextToken(); if (_currentToken.Type == TokenType.Error) { Emit(WarningCode.BadToken, _currentToken.ValueAsString()); @@ -94,12 +97,12 @@ protected TokenType Consume(TokenType[] types, string errorMessage) { /// protected void Warning(string message, Token? token = null) { token ??= _currentToken; - DMCompiler.ForcedWarning(token.Value.Location, message); + Compiler.ForcedWarning(token.Value.Location, message); } /// True if this will raise an error, false if not. You can use this return value to help improve error emission around this (depending on how permissive we're being) protected bool Emit(WarningCode code, Location location, string message) { - return DMCompiler.Emit(code, location, message); + return Compiler.Emit(code, location, message); } protected bool Emit(WarningCode code, string message) { diff --git a/DMCompiler/DM/Builders/DMASTFolder.cs b/DMCompiler/DM/Builders/DMASTFolder.cs index b3058f23a6..0bbe08b1ea 100644 --- a/DMCompiler/DM/Builders/DMASTFolder.cs +++ b/DMCompiler/DM/Builders/DMASTFolder.cs @@ -219,34 +219,6 @@ private DMASTExpression FoldExpression(DMASTExpression? expression) { break; } - case DMASTBinaryAnd binaryAnd: { - if (binaryAnd is { LHS: DMASTConstantInteger lhsInt, RHS: DMASTConstantInteger rhsInt }) { - return new DMASTConstantInteger(expression.Location, lhsInt.Value & rhsInt.Value); - } - - break; - } - case DMASTBinaryOr binaryOr: { - if (binaryOr is { LHS: DMASTConstantInteger lhsInt, RHS: DMASTConstantInteger rhsInt }) { - return new DMASTConstantInteger(expression.Location, lhsInt.Value | rhsInt.Value); - } - - break; - } - case DMASTBinaryNot binaryNot: { - if (binaryNot.Value is DMASTConstantInteger exprInt) { - return new DMASTConstantInteger(expression.Location, (~exprInt.Value) & 0xFFFFFF); - } - - break; - } - case DMASTAdd add: { - DMASTConstantString? lhsString = add.LHS as DMASTConstantString; - DMASTConstantString? rhsString = add.RHS as DMASTConstantString; - if (lhsString != null && rhsString != null) return new DMASTConstantString(expression.Location, lhsString.Value + rhsString.Value); - - break; - } #endregion Math diff --git a/DMCompiler/DM/Builders/DMCodeTreeBuilder.cs b/DMCompiler/DM/Builders/DMCodeTreeBuilder.cs new file mode 100644 index 0000000000..c4b558c95d --- /dev/null +++ b/DMCompiler/DM/Builders/DMCodeTreeBuilder.cs @@ -0,0 +1,75 @@ +using DMCompiler.Compiler.DM.AST; + +namespace DMCompiler.DM.Builders; + +internal class DMCodeTreeBuilder(DMCompiler compiler) { + private bool _leftDMStandard; + + private DMCodeTree CodeTree => compiler.DMCodeTree; + + public void BuildCodeTree(DMASTFile astFile) { + _leftDMStandard = false; + + // Add everything in the AST to the code tree + ProcessBlockInner(astFile.BlockInner, DreamPath.Root); + + // Now define everything in the code tree + CodeTree.DefineEverything(); + if (compiler.Settings.PrintCodeTree) + CodeTree.Print(); + + // Create each types' initialization proc (initializes vars that aren't constants) + foreach (DMObject dmObject in compiler.DMObjectTree.AllObjects) + dmObject.CreateInitializationProc(); + + // Compile every proc + foreach (DMProc proc in compiler.DMObjectTree.AllProcs) + proc.Compile(); + } + + private void ProcessBlockInner(DMASTBlockInner blockInner, DreamPath currentType) { + foreach (DMASTStatement statement in blockInner.Statements) { + ProcessStatement(statement, currentType); + } + } + + private void ProcessStatement(DMASTStatement statement, DreamPath currentType) { + if (!_leftDMStandard && !statement.Location.InDMStandard) { + _leftDMStandard = true; + CodeTree.FinishDMStandard(); + } + + switch (statement) { + case DMASTObjectDefinition objectDefinition: + CodeTree.AddType(objectDefinition.Path); + if (objectDefinition.InnerBlock != null) + ProcessBlockInner(objectDefinition.InnerBlock, objectDefinition.Path); + break; + case DMASTObjectVarDefinition varDefinition: + CodeTree.AddType(varDefinition.ObjectPath); + CodeTree.AddObjectVar(varDefinition.ObjectPath, varDefinition); + break; + case DMASTObjectVarOverride varOverride: + CodeTree.AddType(varOverride.ObjectPath); + CodeTree.AddObjectVarOverride(varOverride.ObjectPath, varOverride); + break; + case DMASTProcDefinition procDefinition: + var procOwner = currentType.Combine(procDefinition.ObjectPath); + + CodeTree.AddType(procOwner); + CodeTree.AddProc(procOwner, procDefinition); + break; + case DMASTMultipleObjectVarDefinitions multipleVarDefinitions: { + foreach (DMASTObjectVarDefinition varDefinition in multipleVarDefinitions.VarDefinitions) { + CodeTree.AddType(varDefinition.ObjectPath); + CodeTree.AddObjectVar(varDefinition.ObjectPath, varDefinition); + } + + break; + } + default: + compiler.ForcedError(statement.Location, $"Invalid object statement {statement.GetType()}"); + break; + } + } +} diff --git a/DMCompiler/DM/Builders/DMExpressionBuilder.cs b/DMCompiler/DM/Builders/DMExpressionBuilder.cs index e51f0ff994..6c72adc963 100644 --- a/DMCompiler/DM/Builders/DMExpressionBuilder.cs +++ b/DMCompiler/DM/Builders/DMExpressionBuilder.cs @@ -2,11 +2,12 @@ using Resource = DMCompiler.DM.Expressions.Resource; using DMCompiler.Compiler.DM.AST; using DMCompiler.DM.Expressions; +using static DMCompiler.DM.Builders.DMExpressionBuilder.ScopeMode; using String = DMCompiler.DM.Expressions.String; namespace DMCompiler.DM.Builders; -internal static class DMExpressionBuilder { +internal class DMExpressionBuilder(ExpressionContext ctx, DMExpressionBuilder.ScopeMode scopeMode = Normal) { public enum ScopeMode { /// All in-scope procs and vars available Normal, @@ -15,335 +16,472 @@ public enum ScopeMode { Static, /// Only global procs available - FirstPassStatic, + FirstPassStatic } - // TODO: Remove these terrible global flags - public static ScopeMode CurrentScopeMode = ScopeMode.Normal; - public static bool ScopeOperatorEnabled = false; // Enabled once var overrides have been processed + // TODO: Remove this terrible global flag + public static bool ScopeOperatorEnabled = false; // Enabled on the last pass of the code tree + + private UnknownReference? _encounteredUnknownReference; + + private DMCompiler Compiler => ctx.Compiler; + private DMObjectTree ObjectTree => ctx.ObjectTree; + + // TODO: proc and dmObject can be null, address nullability contract + public DMExpression Create(DMASTExpression expression, DreamPath? inferredPath = null) { + var expr = CreateIgnoreUnknownReference(expression, inferredPath); + if (expr is UnknownReference unknownRef) + unknownRef.EmitCompilerError(Compiler); + + return expr; + } + + public DMExpression CreateIgnoreUnknownReference(DMASTExpression expression, DreamPath? inferredPath = null) { + _encounteredUnknownReference = null; + return BuildExpression(expression, inferredPath); + } + + public void Emit(DMASTExpression expression, DreamPath? inferredPath = null) { + var expr = Create(expression, inferredPath); + expr.EmitPushValue(ctx); + } + + public bool TryConstant(DMASTExpression expression, out Constant? constant) { + var expr = Create(expression); + return expr.TryAsConstant(Compiler, out constant); + } + + /// Don't use Create() inside this or anything it calls! It resets _encounteredUnknownReference + private DMExpression BuildExpression(DMASTExpression expression, DreamPath? inferredPath = null) { + DMExpression result; - public static DMExpression BuildExpression(DMASTExpression expression, DMObject dmObject, DMProc proc, DreamPath? inferredPath = null) { switch (expression) { case DMASTInvalidExpression: - // No DMCompiler.Emit() here because the parser should have emitted an error when making this + // No error emission here because the parser should have emitted an error when making this return new BadExpression(expression.Location); - case DMASTExpressionConstant constant: return BuildConstant(constant, dmObject, proc); - case DMASTStringFormat stringFormat: return BuildStringFormat(stringFormat, dmObject, proc, inferredPath); - case DMASTIdentifier identifier: return BuildIdentifier(identifier, dmObject, proc, inferredPath); - case DMASTScopeIdentifier globalIdentifier: return BuildScopeIdentifier(globalIdentifier, dmObject, proc, inferredPath); - case DMASTCallableSelf: return new ProcSelf(expression.Location, null, proc); - case DMASTCallableSuper: return new ProcSuper(expression.Location, dmObject, proc); - case DMASTCallableProcIdentifier procIdentifier: return BuildCallableProcIdentifier(procIdentifier, dmObject); - case DMASTProcCall procCall: return BuildProcCall(procCall, dmObject, proc, inferredPath); - case DMASTAssign assign: return BuildAssign(assign, dmObject, proc, inferredPath); - case DMASTAssignInto assignInto: return BuildAssignInto(assignInto, dmObject, proc, inferredPath); - case DMASTEqual equal: return BuildEqual(equal, dmObject, proc, inferredPath); - case DMASTNotEqual notEqual: return BuildNotEqual(notEqual, dmObject, proc, inferredPath); - case DMASTDereference deref: return BuildDereference(deref, dmObject, proc, inferredPath); - case DMASTLocate locate: return BuildLocate(locate, dmObject, proc, inferredPath); - case DMASTImplicitIsType implicitIsType: return BuildImplicitIsType(implicitIsType, dmObject, proc, inferredPath); - case DMASTList list: return BuildList(list, dmObject, proc); - case DMASTDimensionalList dimensionalList: return BuildDimensionalList(dimensionalList, dmObject, proc, inferredPath); - case DMASTNewList newList: return BuildNewList(newList, dmObject, proc, inferredPath); - case DMASTAddText addText: return BuildAddText(addText, dmObject, proc, inferredPath); - case DMASTInput input: return BuildInput(input, dmObject, proc); - case DMASTPick pick: return BuildPick(pick, dmObject, proc); - case DMASTLog log: return BuildLog(log, dmObject, proc, inferredPath); - case DMASTCall call: return BuildCall(call, dmObject, proc, inferredPath); - case DMASTExpressionWrapped wrapped: return BuildExpression(wrapped.Value, dmObject, proc, inferredPath); + case DMASTExpressionConstant constant: result = BuildConstant(constant); break; + case DMASTStringFormat stringFormat: result = BuildStringFormat(stringFormat, inferredPath); break; + case DMASTIdentifier identifier: result = BuildIdentifier(identifier, inferredPath); break; + case DMASTScopeIdentifier globalIdentifier: result = BuildScopeIdentifier(globalIdentifier, inferredPath); break; + case DMASTCallableSelf: result = new ProcSelf(expression.Location, ctx.Proc.ReturnTypes); break; + case DMASTCallableSuper: result = new ProcSuper(expression.Location, ctx.Type.GetProcReturnTypes(ctx.Proc.Name)); break; + case DMASTCallableProcIdentifier procIdentifier: result = BuildCallableProcIdentifier(procIdentifier, ctx.Type); break; + case DMASTProcCall procCall: result = BuildProcCall(procCall, inferredPath); break; + case DMASTAssign assign: result = BuildAssign(assign, inferredPath); break; + case DMASTAssignInto assignInto: result = BuildAssignInto(assignInto, inferredPath); break; + case DMASTEqual equal: result = BuildEqual(equal, inferredPath); break; + case DMASTNotEqual notEqual: result = BuildNotEqual(notEqual, inferredPath); break; + case DMASTDereference deref: result = BuildDereference(deref, inferredPath); break; + case DMASTLocate locate: result = BuildLocate(locate, inferredPath); break; + case DMASTImplicitIsType implicitIsType: result = BuildImplicitIsType(implicitIsType, inferredPath); break; + case DMASTList list: result = BuildList(list); break; + case DMASTDimensionalList dimensionalList: result = BuildDimensionalList(dimensionalList, inferredPath); break; + case DMASTNewList newList: result = BuildNewList(newList, inferredPath); break; + case DMASTAddText addText: result = BuildAddText(addText, inferredPath); break; + case DMASTInput input: result = BuildInput(input); break; + case DMASTPick pick: result = BuildPick(pick); break; + case DMASTLog log: result = BuildLog(log, inferredPath); break; + case DMASTCall call: result = BuildCall(call, inferredPath); break; + case DMASTExpressionWrapped wrapped: result = BuildExpression(wrapped.Value, inferredPath); break; case DMASTNegate negate: - return new Negate(negate.Location, BuildExpression(negate.Value, dmObject, proc, inferredPath)); + result = new Negate(negate.Location, BuildExpression(negate.Value, inferredPath)); + break; case DMASTNot not: - return new Not(not.Location, BuildExpression(not.Value, dmObject, proc, inferredPath)); + result = new Not(not.Location, BuildExpression(not.Value, inferredPath)); + break; case DMASTBinaryNot binaryNot: - return new BinaryNot(binaryNot.Location, BuildExpression(binaryNot.Value, dmObject, proc, inferredPath)); + result = new BinaryNot(binaryNot.Location, BuildExpression(binaryNot.Value, inferredPath)); + break; case DMASTAdd add: - return new Add(add.Location, - BuildExpression(add.LHS, dmObject, proc, inferredPath), - BuildExpression(add.RHS, dmObject, proc, inferredPath)); + result = new Add(add.Location, + BuildExpression(add.LHS, inferredPath), + BuildExpression(add.RHS, inferredPath)); + break; case DMASTSubtract subtract: - return new Subtract(subtract.Location, - BuildExpression(subtract.LHS, dmObject, proc, inferredPath), - BuildExpression(subtract.RHS, dmObject, proc, inferredPath)); + result = new Subtract(subtract.Location, + BuildExpression(subtract.LHS, inferredPath), + BuildExpression(subtract.RHS, inferredPath)); + break; case DMASTMultiply multiply: - return new Multiply(multiply.Location, - BuildExpression(multiply.LHS, dmObject, proc, inferredPath), - BuildExpression(multiply.RHS, dmObject, proc, inferredPath)); + result = new Multiply(multiply.Location, + BuildExpression(multiply.LHS, inferredPath), + BuildExpression(multiply.RHS, inferredPath)); + break; case DMASTDivide divide: - return new Divide(divide.Location, - BuildExpression(divide.LHS, dmObject, proc, inferredPath), - BuildExpression(divide.RHS, dmObject, proc, inferredPath)); + result = new Divide(divide.Location, + BuildExpression(divide.LHS, inferredPath), + BuildExpression(divide.RHS, inferredPath)); + break; case DMASTModulus modulus: - return new Modulo(modulus.Location, - BuildExpression(modulus.LHS, dmObject, proc, inferredPath), - BuildExpression(modulus.RHS, dmObject, proc, inferredPath)); + result = new Modulo(modulus.Location, + BuildExpression(modulus.LHS, inferredPath), + BuildExpression(modulus.RHS, inferredPath)); + break; case DMASTModulusModulus modulusModulus: - return new ModuloModulo(modulusModulus.Location, - BuildExpression(modulusModulus.LHS, dmObject, proc, inferredPath), - BuildExpression(modulusModulus.RHS, dmObject, proc, inferredPath)); + result = new ModuloModulo(modulusModulus.Location, + BuildExpression(modulusModulus.LHS, inferredPath), + BuildExpression(modulusModulus.RHS, inferredPath)); + break; case DMASTPower power: - return new Power(power.Location, - BuildExpression(power.LHS, dmObject, proc, inferredPath), - BuildExpression(power.RHS, dmObject, proc, inferredPath)); + result = new Power(power.Location, + BuildExpression(power.LHS, inferredPath), + BuildExpression(power.RHS, inferredPath)); + break; case DMASTAppend append: - return new Append(append.Location, - BuildExpression(append.LHS, dmObject, proc, inferredPath), - BuildExpression(append.RHS, dmObject, proc, inferredPath)); + result = new Append(append.Location, + BuildExpression(append.LHS, inferredPath), + BuildExpression(append.RHS, inferredPath)); + break; case DMASTCombine combine: - return new Combine(combine.Location, - BuildExpression(combine.LHS, dmObject, proc, inferredPath), - BuildExpression(combine.RHS, dmObject, proc, inferredPath)); + result = new Combine(combine.Location, + BuildExpression(combine.LHS, inferredPath), + BuildExpression(combine.RHS, inferredPath)); + break; case DMASTRemove remove: - return new Remove(remove.Location, - BuildExpression(remove.LHS, dmObject, proc, inferredPath), - BuildExpression(remove.RHS, dmObject, proc, inferredPath)); + result = new Remove(remove.Location, + BuildExpression(remove.LHS, inferredPath), + BuildExpression(remove.RHS, inferredPath)); + break; case DMASTMask mask: - return new Mask(mask.Location, - BuildExpression(mask.LHS, dmObject, proc, inferredPath), - BuildExpression(mask.RHS, dmObject, proc, inferredPath)); + result = new Mask(mask.Location, + BuildExpression(mask.LHS, inferredPath), + BuildExpression(mask.RHS, inferredPath)); + break; case DMASTLogicalAndAssign lAnd: - var lAndLHS = BuildExpression(lAnd.LHS, dmObject, proc, inferredPath); - var lAndRHS = BuildExpression(lAnd.RHS, dmObject, proc, lAndLHS.NestedPath); - return new LogicalAndAssign(lAnd.Location, + var lAndLHS = BuildExpression(lAnd.LHS, inferredPath); + var lAndRHS = BuildExpression(lAnd.RHS, lAndLHS.NestedPath); + + result = new LogicalAndAssign(lAnd.Location, lAndLHS, lAndRHS); + break; case DMASTLogicalOrAssign lOr: - var lOrLHS = BuildExpression(lOr.LHS, dmObject, proc, inferredPath); - var lOrRHS = BuildExpression(lOr.RHS, dmObject, proc, lOrLHS.NestedPath); - return new LogicalOrAssign(lOr.Location, lOrLHS, lOrRHS); + var lOrLHS = BuildExpression(lOr.LHS, inferredPath); + var lOrRHS = BuildExpression(lOr.RHS, lOrLHS.NestedPath); + + result = new LogicalOrAssign(lOr.Location, lOrLHS, lOrRHS); + break; case DMASTMultiplyAssign multiplyAssign: - return new MultiplyAssign(multiplyAssign.Location, - BuildExpression(multiplyAssign.LHS, dmObject, proc, inferredPath), - BuildExpression(multiplyAssign.RHS, dmObject, proc, inferredPath)); + result = new MultiplyAssign(multiplyAssign.Location, + BuildExpression(multiplyAssign.LHS, inferredPath), + BuildExpression(multiplyAssign.RHS, inferredPath)); + break; case DMASTDivideAssign divideAssign: - return new DivideAssign(divideAssign.Location, - BuildExpression(divideAssign.LHS, dmObject, proc, inferredPath), - BuildExpression(divideAssign.RHS, dmObject, proc, inferredPath)); + result = new DivideAssign(divideAssign.Location, + BuildExpression(divideAssign.LHS, inferredPath), + BuildExpression(divideAssign.RHS, inferredPath)); + break; case DMASTLeftShiftAssign leftShiftAssign: - return new LeftShiftAssign(leftShiftAssign.Location, - BuildExpression(leftShiftAssign.LHS, dmObject, proc, inferredPath), - BuildExpression(leftShiftAssign.RHS, dmObject, proc, inferredPath)); + result = new LeftShiftAssign(leftShiftAssign.Location, + BuildExpression(leftShiftAssign.LHS, inferredPath), + BuildExpression(leftShiftAssign.RHS, inferredPath)); + break; case DMASTRightShiftAssign rightShiftAssign: - return new RightShiftAssign(rightShiftAssign.Location, - BuildExpression(rightShiftAssign.LHS, dmObject, proc, inferredPath), - BuildExpression(rightShiftAssign.RHS, dmObject, proc, inferredPath)); + result = new RightShiftAssign(rightShiftAssign.Location, + BuildExpression(rightShiftAssign.LHS, inferredPath), + BuildExpression(rightShiftAssign.RHS, inferredPath)); + break; case DMASTXorAssign xorAssign: - return new XorAssign(xorAssign.Location, - BuildExpression(xorAssign.LHS, dmObject, proc, inferredPath), - BuildExpression(xorAssign.RHS, dmObject, proc, inferredPath)); + result = new XorAssign(xorAssign.Location, + BuildExpression(xorAssign.LHS, inferredPath), + BuildExpression(xorAssign.RHS, inferredPath)); + break; case DMASTModulusAssign modulusAssign: - return new ModulusAssign(modulusAssign.Location, - BuildExpression(modulusAssign.LHS, dmObject, proc, inferredPath), - BuildExpression(modulusAssign.RHS, dmObject, proc, inferredPath)); + result = new ModulusAssign(modulusAssign.Location, + BuildExpression(modulusAssign.LHS, inferredPath), + BuildExpression(modulusAssign.RHS, inferredPath)); + break; case DMASTModulusModulusAssign modulusModulusAssign: - var mmAssignLHS = BuildExpression(modulusModulusAssign.LHS, dmObject, proc, inferredPath); - var mmAssignRHS = BuildExpression(modulusModulusAssign.RHS, dmObject, proc, mmAssignLHS.NestedPath); - return new ModulusModulusAssign(modulusModulusAssign.Location, mmAssignLHS, mmAssignRHS); + var mmAssignLHS = BuildExpression(modulusModulusAssign.LHS, inferredPath); + var mmAssignRHS = BuildExpression(modulusModulusAssign.RHS, mmAssignLHS.NestedPath); + + result = new ModulusModulusAssign(modulusModulusAssign.Location, mmAssignLHS, mmAssignRHS); + break; case DMASTLeftShift leftShift: - return new LeftShift(leftShift.Location, - BuildExpression(leftShift.LHS, dmObject, proc, inferredPath), - BuildExpression(leftShift.RHS, dmObject, proc, inferredPath)); + result = new LeftShift(leftShift.Location, + BuildExpression(leftShift.LHS, inferredPath), + BuildExpression(leftShift.RHS, inferredPath)); + break; case DMASTRightShift rightShift: - return new RightShift(rightShift.Location, - BuildExpression(rightShift.LHS, dmObject, proc, inferredPath), - BuildExpression(rightShift.RHS, dmObject, proc, inferredPath)); + result = new RightShift(rightShift.Location, + BuildExpression(rightShift.LHS, inferredPath), + BuildExpression(rightShift.RHS, inferredPath)); + break; case DMASTBinaryAnd binaryAnd: - return new BinaryAnd(binaryAnd.Location, - BuildExpression(binaryAnd.LHS, dmObject, proc, inferredPath), - BuildExpression(binaryAnd.RHS, dmObject, proc, inferredPath)); + result = new BinaryAnd(binaryAnd.Location, + BuildExpression(binaryAnd.LHS, inferredPath), + BuildExpression(binaryAnd.RHS, inferredPath)); + break; case DMASTBinaryXor binaryXor: - return new BinaryXor(binaryXor.Location, - BuildExpression(binaryXor.LHS, dmObject, proc, inferredPath), - BuildExpression(binaryXor.RHS, dmObject, proc, inferredPath)); + result = new BinaryXor(binaryXor.Location, + BuildExpression(binaryXor.LHS, inferredPath), + BuildExpression(binaryXor.RHS, inferredPath)); + break; case DMASTBinaryOr binaryOr: - return new BinaryOr(binaryOr.Location, - BuildExpression(binaryOr.LHS, dmObject, proc, inferredPath), - BuildExpression(binaryOr.RHS, dmObject, proc, inferredPath)); + result = new BinaryOr(binaryOr.Location, + BuildExpression(binaryOr.LHS, inferredPath), + BuildExpression(binaryOr.RHS, inferredPath)); + break; case DMASTEquivalent equivalent: - return new Equivalent(equivalent.Location, - BuildExpression(equivalent.LHS, dmObject, proc, inferredPath), - BuildExpression(equivalent.RHS, dmObject, proc, inferredPath)); + result = new Equivalent(equivalent.Location, + BuildExpression(equivalent.LHS, inferredPath), + BuildExpression(equivalent.RHS, inferredPath)); + break; case DMASTNotEquivalent notEquivalent: - return new NotEquivalent(notEquivalent.Location, - BuildExpression(notEquivalent.LHS, dmObject, proc, inferredPath), - BuildExpression(notEquivalent.RHS, dmObject, proc, inferredPath)); + result = new NotEquivalent(notEquivalent.Location, + BuildExpression(notEquivalent.LHS, inferredPath), + BuildExpression(notEquivalent.RHS, inferredPath)); + break; case DMASTGreaterThan greaterThan: - return new GreaterThan(greaterThan.Location, - BuildExpression(greaterThan.LHS, dmObject, proc, inferredPath), - BuildExpression(greaterThan.RHS, dmObject, proc, inferredPath)); + result = new GreaterThan(greaterThan.Location, + BuildExpression(greaterThan.LHS, inferredPath), + BuildExpression(greaterThan.RHS, inferredPath)); + break; case DMASTGreaterThanOrEqual greaterThanOrEqual: - return new GreaterThanOrEqual(greaterThanOrEqual.Location, - BuildExpression(greaterThanOrEqual.LHS, dmObject, proc, inferredPath), - BuildExpression(greaterThanOrEqual.RHS, dmObject, proc, inferredPath)); + result = new GreaterThanOrEqual(greaterThanOrEqual.Location, + BuildExpression(greaterThanOrEqual.LHS, inferredPath), + BuildExpression(greaterThanOrEqual.RHS, inferredPath)); + break; case DMASTLessThan lessThan: - return new LessThan(lessThan.Location, - BuildExpression(lessThan.LHS, dmObject, proc, inferredPath), - BuildExpression(lessThan.RHS, dmObject, proc, inferredPath)); + result = new LessThan(lessThan.Location, + BuildExpression(lessThan.LHS, inferredPath), + BuildExpression(lessThan.RHS, inferredPath)); + break; case DMASTLessThanOrEqual lessThanOrEqual: - return new LessThanOrEqual(lessThanOrEqual.Location, - BuildExpression(lessThanOrEqual.LHS, dmObject, proc, inferredPath), - BuildExpression(lessThanOrEqual.RHS, dmObject, proc, inferredPath)); + result = new LessThanOrEqual(lessThanOrEqual.Location, + BuildExpression(lessThanOrEqual.LHS, inferredPath), + BuildExpression(lessThanOrEqual.RHS, inferredPath)); + break; case DMASTOr or: - return new Or(or.Location, - BuildExpression(or.LHS, dmObject, proc, inferredPath), - BuildExpression(or.RHS, dmObject, proc, inferredPath)); + result = new Or(or.Location, + BuildExpression(or.LHS, inferredPath), + BuildExpression(or.RHS, inferredPath)); + break; case DMASTAnd and: - return new And(and.Location, - BuildExpression(and.LHS, dmObject, proc, inferredPath), - BuildExpression(and.RHS, dmObject, proc, inferredPath)); + result = new And(and.Location, + BuildExpression(and.LHS, inferredPath), + BuildExpression(and.RHS, inferredPath)); + break; case DMASTTernary ternary: - return new Ternary(ternary.Location, - BuildExpression(ternary.A, dmObject, proc, inferredPath), - BuildExpression(ternary.B, dmObject, proc, inferredPath), - BuildExpression(ternary.C ?? new DMASTConstantNull(ternary.Location), dmObject, proc, inferredPath)); + var a = BuildExpression(ternary.A, inferredPath); + var b = BuildExpression(ternary.B, inferredPath); + var c = BuildExpression(ternary.C ?? new DMASTConstantNull(ternary.Location), inferredPath); + + if (b.ValType.TypePath != null && c.ValType.TypePath != null && b.ValType.TypePath != c.ValType.TypePath) { + Compiler.Emit(WarningCode.LostTypeInfo, ternary.Location, + $"Ternary has type paths {b.ValType.TypePath} and {c.ValType.TypePath} but a value can only have one type path. Using {b.ValType.TypePath}."); + } + + result = new Ternary(ternary.Location, a, b, c); + break; case DMASTNewPath newPath: - if (BuildExpression(newPath.Path, dmObject, proc, inferredPath) is not ConstantPath path) - return BadExpression(WarningCode.BadExpression, newPath.Path.Location, + if (BuildExpression(newPath.Path, inferredPath) is not IConstantPath path) { + result = BadExpression(WarningCode.BadExpression, newPath.Path.Location, "Expected a path expression"); + break; + } - return new NewPath(newPath.Location, path, - new ArgumentList(newPath.Location, dmObject, proc, newPath.Parameters, inferredPath)); + result = new NewPath(Compiler, newPath.Location, path, + BuildArgumentList(newPath.Location, newPath.Parameters, inferredPath)); + break; case DMASTNewExpr newExpr: - return new New(newExpr.Location, - BuildExpression(newExpr.Expression, dmObject, proc, inferredPath), - new ArgumentList(newExpr.Location, dmObject, proc, newExpr.Parameters, inferredPath)); + result = new New(Compiler, newExpr.Location, + BuildExpression(newExpr.Expression, inferredPath), + BuildArgumentList(newExpr.Location, newExpr.Parameters, inferredPath)); + break; case DMASTNewInferred newInferred: - if (inferredPath is null) - return BadExpression(WarningCode.BadExpression, newInferred.Location, "Could not infer a type"); + if (inferredPath is null) { + result = BadExpression(WarningCode.BadExpression, newInferred.Location, "Could not infer a type"); + break; + } - return new NewPath(newInferred.Location, new ConstantPath(newInferred.Location, dmObject, inferredPath.Value), - new ArgumentList(newInferred.Location, dmObject, proc, newInferred.Parameters, inferredPath)); + var type = BuildPath(newInferred.Location, inferredPath.Value); + if (type is not IConstantPath inferredType) { + result = BadExpression(WarningCode.BadExpression, newInferred.Location, + $"Cannot instantiate {type}"); + break; + } + + result = new NewPath(Compiler, newInferred.Location, inferredType, + BuildArgumentList(newInferred.Location, newInferred.Parameters, inferredPath)); + break; case DMASTPreIncrement preIncrement: - return new PreIncrement(preIncrement.Location, BuildExpression(preIncrement.Value, dmObject, proc, inferredPath)); + result = new PreIncrement(preIncrement.Location, BuildExpression(preIncrement.Value, inferredPath)); + break; case DMASTPostIncrement postIncrement: - return new PostIncrement(postIncrement.Location, BuildExpression(postIncrement.Value, dmObject, proc, inferredPath)); + result = new PostIncrement(postIncrement.Location, BuildExpression(postIncrement.Value, inferredPath)); + break; case DMASTPreDecrement preDecrement: - return new PreDecrement(preDecrement.Location, BuildExpression(preDecrement.Value, dmObject, proc, inferredPath)); + result = new PreDecrement(preDecrement.Location, BuildExpression(preDecrement.Value, inferredPath)); + break; case DMASTPostDecrement postDecrement: - return new PostDecrement(postDecrement.Location, BuildExpression(postDecrement.Value, dmObject, proc, inferredPath)); + result = new PostDecrement(postDecrement.Location, BuildExpression(postDecrement.Value, inferredPath)); + break; case DMASTPointerRef pointerRef: - return new PointerRef(pointerRef.Location, BuildExpression(pointerRef.Value, dmObject, proc, inferredPath)); + result = new PointerRef(pointerRef.Location, BuildExpression(pointerRef.Value, inferredPath)); + break; case DMASTPointerDeref pointerDeref: - return new PointerDeref(pointerDeref.Location, BuildExpression(pointerDeref.Value, dmObject, proc, inferredPath)); + result = new PointerDeref(pointerDeref.Location, BuildExpression(pointerDeref.Value, inferredPath)); + break; case DMASTGradient gradient: - return new Gradient(gradient.Location, - new ArgumentList(gradient.Location, dmObject, proc, gradient.Parameters)); + result = new Gradient(gradient.Location, + BuildArgumentList(gradient.Location, gradient.Parameters)); + break; case DMASTRgb rgb: - return new Rgb(rgb.Location, new ArgumentList(rgb.Location, dmObject, proc, rgb.Parameters)); + result = new Rgb(rgb.Location, BuildArgumentList(rgb.Location, rgb.Parameters)); + break; case DMASTLocateCoordinates locateCoordinates: - return new LocateCoordinates(locateCoordinates.Location, - BuildExpression(locateCoordinates.X, dmObject, proc, inferredPath), - BuildExpression(locateCoordinates.Y, dmObject, proc, inferredPath), - BuildExpression(locateCoordinates.Z, dmObject, proc, inferredPath)); + result = new LocateCoordinates(locateCoordinates.Location, + BuildExpression(locateCoordinates.X, inferredPath), + BuildExpression(locateCoordinates.Y, inferredPath), + BuildExpression(locateCoordinates.Z, inferredPath)); + break; case DMASTIsSaved isSaved: - return new IsSaved(isSaved.Location, BuildExpression(isSaved.Value, dmObject, proc, inferredPath)); + result = new IsSaved(isSaved.Location, BuildExpression(isSaved.Value, inferredPath)); + break; case DMASTIsType isType: { if (isType.RHS is DMASTIdentifier { Identifier: "__IMPLIED_TYPE__" }) { - var expr = DMExpression.Create(dmObject, proc, isType.LHS, inferredPath); - if (expr.Path is null) - return BadExpression(WarningCode.BadExpression, isType.Location, "A type could not be inferred!"); + var expr = BuildExpression(isType.LHS, inferredPath); + if (expr.Path is null) { + result = BadExpression(WarningCode.BadExpression, isType.Location, + "A type could not be inferred!"); + break; + } - return new IsTypeInferred(isType.Location, expr, expr.Path.Value); + result = new IsTypeInferred(isType.Location, expr, expr.Path.Value); + break; } - return new IsType(isType.Location, - BuildExpression(isType.LHS, dmObject, proc, inferredPath), - BuildExpression(isType.RHS, dmObject, proc, inferredPath)); + + result = new IsType(isType.Location, + BuildExpression(isType.LHS, inferredPath), + BuildExpression(isType.RHS, inferredPath)); + break; } case DMASTIsNull isNull: - return new IsNull(isNull.Location, BuildExpression(isNull.Value, dmObject, proc, inferredPath)); + result = new IsNull(isNull.Location, BuildExpression(isNull.Value, inferredPath)); + break; case DMASTLength length: - return new Length(length.Location, BuildExpression(length.Value, dmObject, proc, inferredPath)); + result = new Length(length.Location, BuildExpression(length.Value, inferredPath)); + break; case DMASTGetStep getStep: - return new GetStep(getStep.Location, - BuildExpression(getStep.LHS, dmObject, proc, inferredPath), - BuildExpression(getStep.RHS, dmObject, proc, inferredPath)); + result = new GetStep(getStep.Location, + BuildExpression(getStep.LHS, inferredPath), + BuildExpression(getStep.RHS, inferredPath)); + break; case DMASTGetDir getDir: - return new GetDir(getDir.Location, - BuildExpression(getDir.LHS, dmObject, proc, inferredPath), - BuildExpression(getDir.RHS, dmObject, proc, inferredPath)); + result = new GetDir(getDir.Location, + BuildExpression(getDir.LHS, inferredPath), + BuildExpression(getDir.RHS, inferredPath)); + break; case DMASTProb prob: - return new Prob(prob.Location, - BuildExpression(prob.Value, dmObject, proc, inferredPath)); + result = new Prob(prob.Location, + BuildExpression(prob.Value, inferredPath)); + break; case DMASTInitial initial: - return new Initial(initial.Location, BuildExpression(initial.Value, dmObject, proc, inferredPath)); + result = new Initial(initial.Location, BuildExpression(initial.Value, inferredPath)); + break; case DMASTNameof nameof: - return BuildNameof(nameof, dmObject, proc, inferredPath); + result = BuildNameof(nameof, inferredPath); + break; case DMASTExpressionIn expressionIn: - var exprInLHS = BuildExpression(expressionIn.LHS, dmObject, proc, inferredPath); - var exprInRHS = BuildExpression(expressionIn.RHS, dmObject, proc, inferredPath); + var exprInLHS = BuildExpression(expressionIn.LHS, inferredPath); + var exprInRHS = BuildExpression(expressionIn.RHS, inferredPath); if ((expressionIn.LHS is not DMASTExpressionWrapped && exprInLHS is UnaryOp or BinaryOp or Ternary) || (expressionIn.RHS is not DMASTExpressionWrapped && exprInRHS is BinaryOp or Ternary)) { - DMCompiler.Emit(WarningCode.AmbiguousInOrder, expressionIn.Location, + Compiler.Emit(WarningCode.AmbiguousInOrder, expressionIn.Location, "Order of operations for \"in\" may not be what is expected. Use parentheses to be more explicit."); } - return new In(expressionIn.Location, exprInLHS, exprInRHS); + result = new In(expressionIn.Location, exprInLHS, exprInRHS); + break; case DMASTExpressionInRange expressionInRange: - return new InRange(expressionInRange.Location, - BuildExpression(expressionInRange.Value, dmObject, proc, inferredPath), - BuildExpression(expressionInRange.StartRange, dmObject, proc, inferredPath), - BuildExpression(expressionInRange.EndRange, dmObject, proc, inferredPath)); + result = new InRange(expressionInRange.Location, + BuildExpression(expressionInRange.Value, inferredPath), + BuildExpression(expressionInRange.StartRange, inferredPath), + BuildExpression(expressionInRange.EndRange, inferredPath)); + break; case DMASTSin sin: - return new Sin(sin.Location, BuildExpression(sin.Value, dmObject, proc, inferredPath)); + result = new Sin(sin.Location, BuildExpression(sin.Value, inferredPath)); + break; case DMASTCos cos: - return new Cos(cos.Location, BuildExpression(cos.Value, dmObject, proc, inferredPath)); + result = new Cos(cos.Location, BuildExpression(cos.Value, inferredPath)); + break; case DMASTTan tan: - return new Tan(tan.Location, BuildExpression(tan.Value, dmObject, proc, inferredPath)); + result = new Tan(tan.Location, BuildExpression(tan.Value, inferredPath)); + break; case DMASTArcsin arcSin: - return new ArcSin(arcSin.Location, BuildExpression(arcSin.Value, dmObject, proc, inferredPath)); + result = new ArcSin(arcSin.Location, BuildExpression(arcSin.Value, inferredPath)); + break; case DMASTArccos arcCos: - return new ArcCos(arcCos.Location, BuildExpression(arcCos.Value, dmObject, proc, inferredPath)); + result = new ArcCos(arcCos.Location, BuildExpression(arcCos.Value, inferredPath)); + break; case DMASTArctan arcTan: - return new ArcTan(arcTan.Location, BuildExpression(arcTan.Value, dmObject, proc, inferredPath)); + result = new ArcTan(arcTan.Location, BuildExpression(arcTan.Value, inferredPath)); + break; case DMASTArctan2 arcTan2: - return new ArcTan2(arcTan2.Location, - BuildExpression(arcTan2.LHS, dmObject, proc, inferredPath), - BuildExpression(arcTan2.RHS, dmObject, proc, inferredPath)); + result = new ArcTan2(arcTan2.Location, + BuildExpression(arcTan2.LHS, inferredPath), + BuildExpression(arcTan2.RHS, inferredPath)); + break; case DMASTSqrt sqrt: - return new Sqrt(sqrt.Location, BuildExpression(sqrt.Value, dmObject, proc, inferredPath)); + result = new Sqrt(sqrt.Location, BuildExpression(sqrt.Value, inferredPath)); + break; case DMASTAbs abs: - return new Abs(abs.Location, BuildExpression(abs.Value, dmObject, proc, inferredPath)); - + result = new Abs(abs.Location, BuildExpression(abs.Value, inferredPath)); + break; case DMASTVarDeclExpression varDeclExpr: var declIdentifier = new DMASTIdentifier(expression.Location, varDeclExpr.DeclPath.Path.LastElement); - return BuildIdentifier(declIdentifier, dmObject, proc); + + result = BuildIdentifier(declIdentifier); + break; case DMASTVoid: - return BadExpression(WarningCode.BadExpression, expression.Location, "Attempt to use a void expression"); + result = BadExpression(WarningCode.BadExpression, expression.Location, "Attempt to use a void expression"); + break; + default: + throw new ArgumentException($"Invalid expression {expression}", nameof(expression)); } - throw new ArgumentException($"Invalid expression {expression}", nameof(expression)); + if (_encounteredUnknownReference != null) { + return _encounteredUnknownReference; + } else { + return result; + } } - private static DMExpression BuildConstant(DMASTExpressionConstant constant, DMObject dmObject, DMProc proc) { + private DMExpression BuildConstant(DMASTExpressionConstant constant) { switch (constant) { case DMASTConstantNull: return new Null(constant.Location); case DMASTConstantInteger constInt: return new Number(constant.Location, constInt.Value); case DMASTConstantFloat constFloat: return new Number(constant.Location, constFloat.Value); case DMASTConstantString constString: return new String(constant.Location, constString.Value); - case DMASTConstantResource constResource: return new Resource(constant.Location, constResource.Path); - case DMASTConstantPath constPath: return new ConstantPath(constant.Location, dmObject, constPath.Value.Path); + case DMASTConstantResource constResource: return new Resource(Compiler, constant.Location, constResource.Path); + case DMASTConstantPath constPath: return BuildPath(constant.Location, constPath.Value.Path); case DMASTUpwardPathSearch upwardSearch: - DMExpression.TryConstant(dmObject, proc, upwardSearch.Path, out var pathExpr); - if (pathExpr is not ConstantPath expr) + BuildExpression(upwardSearch.Path).TryAsConstant(Compiler, out var pathExpr); + if (pathExpr is not IConstantPath expr) return BadExpression(WarningCode.BadExpression, constant.Location, $"Cannot do an upward path search on {pathExpr}"); - DreamPath path = expr.Value; - DreamPath? foundPath = DMObjectTree.UpwardSearch(path, upwardSearch.Search.Path); + var path = expr.Path; + if (path == null) + return UnknownReference(constant.Location, + $"Cannot search on {expr}"); + + DreamPath? foundPath = ObjectTree.UpwardSearch(path.Value, upwardSearch.Search.Path); if (foundPath == null) - return BadExpression(WarningCode.ItemDoesntExist, constant.Location, + return UnknownReference(constant.Location, $"Could not find path {path}.{upwardSearch.Search.Path}"); - return new ConstantPath(constant.Location, dmObject, foundPath.Value); + return BuildPath(constant.Location, foundPath.Value); } throw new ArgumentException($"Invalid constant {constant}", nameof(constant)); } - private static StringFormat BuildStringFormat(DMASTStringFormat stringFormat, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { + private StringFormat BuildStringFormat(DMASTStringFormat stringFormat, DreamPath? inferredPath) { var expressions = new DMExpression[stringFormat.InterpolatedValues.Length]; for (int i = 0; i < stringFormat.InterpolatedValues.Length; i++) { @@ -352,86 +490,135 @@ private static StringFormat BuildStringFormat(DMASTStringFormat stringFormat, DM if (interpolatedValue == null) { expressions[i] = new Null(stringFormat.Location); } else { - expressions[i] = DMExpression.Create(dmObject, proc, interpolatedValue, inferredPath); + expressions[i] = BuildExpression(interpolatedValue, inferredPath); } } return new StringFormat(stringFormat.Location, stringFormat.Value, expressions); } - private static DMExpression BuildIdentifier(DMASTIdentifier identifier, DMObject dmObject, DMProc proc, DreamPath? inferredPath = null) { + private DMExpression BuildPath(Location location, DreamPath path) { + // An upward search with no left-hand side + if (path.Type == DreamPath.PathType.UpwardSearch) { + DreamPath? foundPath = Compiler.DMCodeTree.UpwardSearch(ctx.Type, path); + if (foundPath == null) + return UnknownReference(location, $"Could not find path {path}"); + + path = foundPath.Value; + } + + // /datum/proc or /datum/verb + if (path.LastElement is "proc" or "verb") { + DreamPath typePath = path.FromElements(0, -2); + if (!ObjectTree.TryGetDMObject(typePath, out var stubOfType)) + return UnknownReference(location, $"Type {typePath} does not exist"); + + return new ConstantProcStub(location, stubOfType, path.LastElement is "verb"); + } + + // /datum + if (ObjectTree.TryGetDMObject(path, out var referencing)) { + return new ConstantTypeReference(location, referencing); + } + + // /datum/proc/foo + int procIndex = path.FindElement("proc"); + if (procIndex == -1) procIndex = path.FindElement("verb"); + if (procIndex != -1) { + DreamPath withoutProcElement = path.RemoveElement(procIndex); + DreamPath ownerPath = withoutProcElement.FromElements(0, -2); + string procName = path.LastElement!; + + if (!ObjectTree.TryGetDMObject(ownerPath, out var owner)) + return UnknownReference(location, $"Type {ownerPath} does not exist"); + + int? procId; + if (owner == ObjectTree.Root && ObjectTree.TryGetGlobalProc(procName, out var globalProc)) { + procId = globalProc.Id; + } else { + var procs = owner.GetProcs(procName); + + procId = procs?[^1]; + } + + if (procId == null || ObjectTree.AllProcs.Count < procId) { + return UnknownReference(location, $"Could not find proc {procName}() on {ownerPath}"); + } + + return new ConstantProcReference(location, path, ObjectTree.AllProcs[procId.Value]); + } + + return UnknownReference(location, $"Path {path} does not exist"); + } + + private DMExpression BuildIdentifier(DMASTIdentifier identifier, DreamPath? inferredPath = null) { var name = identifier.Identifier; switch (name) { case "src": - return new Src(identifier.Location, dmObject.Path); + return new Src(identifier.Location, ctx.Type.Path); case "usr": return new Usr(identifier.Location); case "args": return new Args(identifier.Location); + case "world": + if (scopeMode == FirstPassStatic) // world is not available on the first pass + return UnknownIdentifier(identifier.Location, "world"); + + return new World(identifier.Location); case "__TYPE__": - return new ProcOwnerType(identifier.Location, dmObject); + return new ProcOwnerType(identifier.Location, ctx.Type); case "__IMPLIED_TYPE__": if (inferredPath == null) return BadExpression(WarningCode.BadExpression, identifier.Location, "__IMPLIED_TYPE__ cannot be used here, there is no type being implied"); - return new ConstantPath(identifier.Location, dmObject, inferredPath.Value); + return BuildPath(identifier.Location, inferredPath.Value); case "__PROC__": // The saner alternative to "....." - return new ConstantProcReference(identifier.Location, proc); + var path = ctx.Type.Path.AddToPath("proc/" + ctx.Proc.Name); + + return new ConstantProcReference(identifier.Location, path, ctx.Proc); case "global": return new Global(identifier.Location); default: { - if (CurrentScopeMode == ScopeMode.Normal) { - var localVar = proc?.GetLocalVariable(name); + if (scopeMode == Normal) { + var localVar = ctx.Proc?.GetLocalVariable(name); if (localVar != null) return new Local(identifier.Location, localVar); - - var field = dmObject?.GetVariable(name); - if (field != null) { - return new Field(identifier.Location, field, field.ValType); - } } - if (CurrentScopeMode != ScopeMode.FirstPassStatic) { - var globalId = proc?.GetGlobalVariableId(name) ?? dmObject?.GetGlobalVariableId(name); + var field = ctx.Type.GetVariable(name); + if (field != null && (scopeMode == Normal || field.IsConst)) { + return new Field(identifier.Location, field, field.ValType); + } - if (globalId != null) { - var globalVar = DMObjectTree.Globals[globalId.Value]; - var global = new GlobalField(identifier.Location, globalVar.Type, globalId.Value, globalVar.ValType); - return global; - } + var globalId = ctx.Proc?.GetGlobalVariableId(name) ?? ctx.Type.GetGlobalVariableId(name); - var field = dmObject?.GetVariable(name); - if (field != null) { - if (field.IsConst) - return new Field(identifier.Location, field, field.ValType); + if (globalId != null) { + if (field is not null) + Compiler.Emit(WarningCode.AmbiguousVarStatic, identifier.Location, $"Static var definition cannot reference instance variable \"{name}\" but a global exists"); - return BadExpression(WarningCode.BadExpression, identifier.Location, - "Var \"{name}\" cannot be used in this context"); - } + var globalVar = ObjectTree.Globals[globalId.Value]; + var global = new GlobalField(identifier.Location, globalVar.Type, globalId.Value, globalVar.ValType); + return global; } - throw new UnknownIdentifierException(identifier.Location, name); + return UnknownIdentifier(identifier.Location, name); } } } - private static DMExpression BuildScopeIdentifier( - DMASTScopeIdentifier scopeIdentifier, - DMObject dmObject, DMProc proc, - DreamPath? inferredPath) { + private DMExpression BuildScopeIdentifier(DMASTScopeIdentifier scopeIdentifier, DreamPath? inferredPath) { var location = scopeIdentifier.Location; var bIdentifier = scopeIdentifier.Identifier; if (scopeIdentifier.Expression == null) { // ::A, shorthand for global.A if (scopeIdentifier.IsProcRef) { // ::A(), global proc ref - if (!DMObjectTree.TryGetGlobalProc(bIdentifier, out _)) - return BadExpression(WarningCode.ItemDoesntExist, location, - $"No global proc named \"{bIdentifier}\" exists"); + if (!ObjectTree.TryGetGlobalProc(bIdentifier, out var globalProc)) + return UnknownReference(location, $"No global proc named \"{bIdentifier}\" exists"); - var arguments = new ArgumentList(location, dmObject, proc, scopeIdentifier.CallArguments, inferredPath); - return new ProcCall(location, new GlobalProc(location, bIdentifier), arguments, DMValueType.Anything); + var arguments = BuildArgumentList(location, scopeIdentifier.CallArguments, inferredPath); + return new ProcCall(location, new GlobalProc(location, globalProc), arguments, DMValueType.Anything); } // ::vars, special case @@ -439,55 +626,53 @@ private static DMExpression BuildScopeIdentifier( return new GlobalVars(location); // ::A, global var ref - var globalId = DMObjectTree.Root.GetGlobalVariableId(bIdentifier); + var globalId = ObjectTree.Root.GetGlobalVariableId(bIdentifier); if (globalId == null) - throw new UnknownIdentifierException(location, bIdentifier); + return UnknownIdentifier(location, bIdentifier); - var globalVar = DMObjectTree.Globals[globalId.Value]; + var globalVar = ObjectTree.Globals [globalId.Value]; return new GlobalField(location, - DMObjectTree.Globals[globalId.Value].Type, + ObjectTree.Globals[globalId.Value].Type, globalId.Value, globalVar.ValType); } // Other uses should wait until the scope operator pass if (!ScopeOperatorEnabled) - throw new UnknownIdentifierException(location, bIdentifier); + return UnknownIdentifier(location, bIdentifier); DMExpression? expression; - // "type" and "parent_type" cannot resolve in a static context but it's still valid with scope identifiers + // "type" and "parent_type" cannot resolve in a static context, but it's still valid with scope identifiers if (scopeIdentifier.Expression is DMASTIdentifier { Identifier: "type" or "parent_type" } identifier) { // This is the same behaviour as in BYOND, but BYOND simply raises an undefined var error. // We want to give end users an explanation at least. - if (CurrentScopeMode is ScopeMode.Normal && proc != null) + if (scopeMode is Normal && ctx.Proc != null) return BadExpression(WarningCode.BadExpression, identifier.Location, - "Use of \"type::\" and \"parent_type::\" outside of a static context is forbidden"); + "Use of \"type::\" and \"parent_type::\" outside of a context is forbidden"); if (identifier.Identifier == "parent_type") { - if (dmObject.Parent == null) + if (ctx.Type.Parent == null) return BadExpression(WarningCode.ItemDoesntExist, identifier.Location, - $"Type {dmObject.Path} does not have a parent"); + $"Type {ctx.Type.Path} does not have a parent"); - expression = new ConstantPath(location, dmObject, dmObject.Parent.Path); + expression = BuildPath(location, ctx.Type.Parent.Path); } else { // "type" - expression = new ConstantPath(location, dmObject, dmObject.Path); + expression = BuildPath(location, ctx.Type.Path); } } else { - expression = DMExpression.Create(dmObject, proc, scopeIdentifier.Expression, inferredPath); + expression = BuildExpression(scopeIdentifier.Expression, inferredPath); } - // A must have a type + // A needs to have a type if (expression.Path == null) return BadExpression(WarningCode.BadExpression, expression.Location, - $"Identifier \"{expression.GetNameof(dmObject)}\" does not have a type"); + $"Identifier \"{expression.GetNameof(ctx)}\" does not have a type"); - var owner = DMObjectTree.GetDMObject(expression.Path.Value, createIfNonexistent: false); - if (owner == null) { - if (expression is ConstantPath path && path.TryResolvePath(out var pathInfo) && - pathInfo.Value.Type == ConstantPath.PathType.ProcReference) { + if (!ObjectTree.TryGetDMObject(expression.Path.Value, out var owner)) { + if (expression is ConstantProcReference procReference) { if (bIdentifier == "name") - return new String(expression.Location, path.Path!.Value.LastElement!); + return new String(expression.Location, procReference.Value.Name); return BadExpression(WarningCode.PointlessScopeOperator, expression.Location, "scope operator returns null on proc variables other than \"name\""); @@ -503,62 +688,72 @@ private static DMExpression BuildScopeIdentifier( return BadExpression(WarningCode.ItemDoesntExist, location, $"Type {owner.Path} does not have a proc named \"{bIdentifier}\""); - var referencedProc = DMObjectTree.AllProcs[procs[^1]]; - return new ConstantProcReference(location, referencedProc); + var referencedProc = ObjectTree.AllProcs[procs[^1]]; + var path = owner.Path.AddToPath("proc/" + referencedProc.Name); + return new ConstantProcReference(location, path, referencedProc); } else { // A::B var globalVarId = owner.GetGlobalVariableId(bIdentifier); if (globalVarId != null) { // B is a static var. // This is the only case a ScopeIdentifier can be an LValue. - var globalVar = DMObjectTree.Globals[globalVarId.Value]; + var globalVar = ObjectTree.Globals [globalVarId.Value]; return new GlobalField(location, globalVar.Type, globalVarId.Value, globalVar.ValType); } var variable = owner.GetVariable(bIdentifier); if (variable == null) - throw new UnknownIdentifierException(location, bIdentifier); + return UnknownIdentifier(location, bIdentifier); - return new ScopeReference(location, expression, bIdentifier, variable); + return new ScopeReference(ObjectTree, location, expression, bIdentifier, variable); } } - private static DMExpression BuildCallableProcIdentifier(DMASTCallableProcIdentifier procIdentifier, DMObject dmObject) { - if (CurrentScopeMode is ScopeMode.Static or ScopeMode.FirstPassStatic) - return new GlobalProc(procIdentifier.Location, procIdentifier.Identifier); - if (dmObject.HasProc(procIdentifier.Identifier)) + private DMExpression BuildCallableProcIdentifier(DMASTCallableProcIdentifier procIdentifier, DMObject dmObject) { + if (scopeMode is Static or FirstPassStatic) { + if (!ObjectTree.TryGetGlobalProc(procIdentifier.Identifier, out var staticScopeGlobalProc)) + return UnknownReference(procIdentifier.Location, + $"Type {dmObject.Path} does not have a proc named \"{procIdentifier.Identifier}\""); + + return new GlobalProc(procIdentifier.Location, staticScopeGlobalProc); + } + + if (dmObject.HasProc(procIdentifier.Identifier)) { return new Proc(procIdentifier.Location, procIdentifier.Identifier); - if (DMObjectTree.TryGetGlobalProc(procIdentifier.Identifier, out _)) - return new GlobalProc(procIdentifier.Location, procIdentifier.Identifier); + } - return BadExpression(WarningCode.ItemDoesntExist, procIdentifier.Location, + if (ObjectTree.TryGetGlobalProc(procIdentifier.Identifier, out var globalProc)) { + return new GlobalProc(procIdentifier.Location, globalProc); + } + + return UnknownReference(procIdentifier.Location, $"Type {dmObject.Path} does not have a proc named \"{procIdentifier.Identifier}\""); } - private static DMExpression BuildProcCall(DMASTProcCall procCall, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { + private DMExpression BuildProcCall(DMASTProcCall procCall, DreamPath? inferredPath) { // arglist hack if (procCall.Callable is DMASTCallableProcIdentifier { Identifier: "arglist" }) { switch (procCall.Parameters.Length) { case 0: - DMCompiler.Emit(WarningCode.BadArgument, procCall.Location, "arglist() requires 1 argument"); + Compiler.Emit(WarningCode.BadArgument, procCall.Location, "arglist() requires 1 argument"); break; case 1: break; default: - DMCompiler.Emit( + Compiler.Emit( WarningCode.InvalidArgumentCount, procCall.Location, $"arglist() given {procCall.Parameters.Length} arguments, expecting 1"); break; } - var expr = DMExpression.Create(dmObject, proc, procCall.Parameters[0].Value, inferredPath); + var expr = BuildExpression(procCall.Parameters[0].Value, inferredPath); return new Arglist(procCall.Location, expr); } - var target = DMExpression.Create(dmObject, proc, (DMASTExpression)procCall.Callable, inferredPath); - var args = new ArgumentList(procCall.Location, dmObject, proc, procCall.Parameters); + var target = BuildExpression((DMASTExpression)procCall.Callable, inferredPath); + var args = BuildArgumentList(procCall.Location, procCall.Parameters); if (target is Proc targetProc) { // GlobalProc handles returnType itself - var returnType = targetProc.GetReturnType(dmObject); + var returnType = targetProc.GetReturnType(ctx.Type); return new ProcCall(procCall.Location, target, args, returnType); } @@ -566,29 +761,81 @@ private static DMExpression BuildProcCall(DMASTProcCall procCall, DMObject dmObj return new ProcCall(procCall.Location, target, args, DMValueType.Anything); } - private static DMExpression BuildAssign(DMASTAssign assign, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var lhs = DMExpression.Create(dmObject, proc, assign.LHS, inferredPath); - var rhs = DMExpression.Create(dmObject, proc, assign.RHS, lhs.NestedPath); - if(lhs.TryAsConstant(out _)) { - DMCompiler.Emit(WarningCode.WriteToConstant, assign.LHS.Location, "Cannot write to const var"); + private ArgumentList BuildArgumentList(Location location, DMASTCallParameter[]? arguments, DreamPath? inferredPath = null) { + if (arguments == null || arguments.Length == 0) + return new ArgumentList(location, [], false); + + var expressions = new (string?, DMExpression)[arguments.Length]; + bool isKeyed = false; + + int idx = 0; + foreach(var arg in arguments) { + var value = BuildExpression(arg.Value, inferredPath); + var key = (arg.Key != null) ? BuildExpression(arg.Key, inferredPath) : null; + int argIndex = idx++; + string? name = null; + + switch (key) { + case String keyStr: + name = keyStr.Value; + break; + case Number keyNum: + //Replaces an ordered argument + var newIdx = (int)keyNum.Value - 1; + + if (newIdx == argIndex) { + Compiler.Emit(WarningCode.PointlessPositionalArgument, key.Location, + $"The argument at index {argIndex + 1} is a positional argument with a redundant index (\"{argIndex + 1} = value\" at argument {argIndex + 1}). This does not function like a named argument and is likely a mistake."); + } + + argIndex = newIdx; + break; + case Resource: + case IConstantPath: + //The key becomes the value + value = key; + break; + + default: + if (key != null && key is not Expressions.UnknownReference) { + Compiler.Emit(WarningCode.InvalidArgumentKey, key.Location, $"Invalid argument key {key}"); + } + + break; + } + + if (name != null) + isKeyed = true; + + expressions[argIndex] = (name, value); + } + + return new ArgumentList(location, expressions, isKeyed); + } + + private DMExpression BuildAssign(DMASTAssign assign, DreamPath? inferredPath) { + var lhs = BuildExpression(assign.LHS, inferredPath); + var rhs = BuildExpression(assign.RHS, lhs.NestedPath); + if(lhs.TryAsConstant(Compiler, out _)) { + Compiler.Emit(WarningCode.WriteToConstant, assign.LHS.Location, "Cannot write to const var"); } return new Assignment(assign.Location, lhs, rhs); } - private static DMExpression BuildAssignInto(DMASTAssignInto assign, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var lhs = DMExpression.Create(dmObject, proc, assign.LHS, inferredPath); - var rhs = DMExpression.Create(dmObject, proc, assign.RHS, lhs.NestedPath); - if(lhs.TryAsConstant(out _)) { - DMCompiler.Emit(WarningCode.WriteToConstant, assign.LHS.Location, "Cannot write to const var"); + private DMExpression BuildAssignInto(DMASTAssignInto assign, DreamPath? inferredPath) { + var lhs = BuildExpression(assign.LHS, inferredPath); + var rhs = BuildExpression(assign.RHS, lhs.NestedPath); + if(lhs.TryAsConstant(Compiler, out _)) { + Compiler.Emit(WarningCode.WriteToConstant, assign.LHS.Location, "Cannot write to const var"); } return new AssignmentInto(assign.Location, lhs, rhs); } - private static DMExpression BuildEqual(DMASTEqual equal, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var lhs = DMExpression.Create(dmObject, proc, equal.LHS, inferredPath); - var rhs = DMExpression.Create(dmObject, proc, equal.RHS, inferredPath); + private DMExpression BuildEqual(DMASTEqual equal, DreamPath? inferredPath) { + var lhs = BuildExpression(equal.LHS, inferredPath); + var rhs = BuildExpression(equal.RHS, inferredPath); // (x == null) can be changed to isnull(x) which compiles down to an opcode // TODO: Bytecode optimizations instead @@ -598,9 +845,9 @@ private static DMExpression BuildEqual(DMASTEqual equal, DMObject dmObject, DMPr return new Equal(equal.Location, lhs, rhs); } - private static DMExpression BuildNotEqual(DMASTNotEqual notEqual, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var lhs = DMExpression.Create(dmObject, proc, notEqual.LHS, inferredPath); - var rhs = DMExpression.Create(dmObject, proc, notEqual.RHS, inferredPath); + private DMExpression BuildNotEqual(DMASTNotEqual notEqual, DreamPath? inferredPath) { + var lhs = BuildExpression(notEqual.LHS, inferredPath); + var rhs = BuildExpression(notEqual.RHS, inferredPath); // (x != null) can be changed to !isnull(x) which compiles down to two opcodes // TODO: Bytecode optimizations instead @@ -610,12 +857,12 @@ private static DMExpression BuildNotEqual(DMASTNotEqual notEqual, DMObject dmObj return new NotEqual(notEqual.Location, lhs, rhs); } - private static DMExpression BuildDereference(DMASTDereference deref, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { + private DMExpression BuildDereference(DMASTDereference deref, DreamPath? inferredPath) { var astOperations = deref.Operations; // The base expression and list of operations to perform on it // These may be redefined if we encounter a global access mid-operation - var expr = DMExpression.Create(dmObject, proc, deref.Expression, inferredPath); + var expr = BuildExpression(deref.Expression, inferredPath); var operations = new Dereference.Operation[deref.Operations.Length]; int astOperationOffset = 0; @@ -634,11 +881,14 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm switch (namedOperation) { // global.f() case DMASTDereference.CallOperation callOperation: - ArgumentList argumentList = new(deref.Expression.Location, dmObject, proc, - callOperation.Parameters); + if (!ObjectTree.TryGetGlobalProc(callOperation.Identifier, out var globalProc)) + return UnknownReference(callOperation.Location, + $"Could not find a global proc named \"{callOperation.Identifier}\""); + + var argumentList = BuildArgumentList(deref.Expression.Location, callOperation.Parameters); - var globalProc = new GlobalProc(expr.Location, callOperation.Identifier); - expr = new ProcCall(expr.Location, globalProc, argumentList, DMValueType.Anything); + var globalProcExpr = new GlobalProc(expr.Location, globalProc); + expr = new ProcCall(expr.Location, globalProcExpr, argumentList, DMValueType.Anything); break; case DMASTDereference.FieldOperation: @@ -649,12 +899,11 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm } // global.variable - var globalId = dmObject.GetGlobalVariableId(namedOperation.Identifier); - if (globalId == null) { - throw new UnknownIdentifierException(deref.Location, $"global.{namedOperation.Identifier}"); - } + var globalId = ctx.Type.GetGlobalVariableId(namedOperation.Identifier); + if (globalId == null) + return UnknownIdentifier(deref.Location, $"global.{namedOperation.Identifier}"); - var property = DMObjectTree.Globals[globalId.Value]; + var property = ObjectTree.Globals [globalId.Value]; expr = new GlobalField(expr.Location, property.Type, globalId.Value, property.ValType); prevPath = property.Type; @@ -672,7 +921,7 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm operations = new Dereference.Operation[newOperationCount]; astOperationOffset = 1; } else { - DMCompiler.Emit(WarningCode.BadExpression, firstOperation.Location, + Compiler.Emit(WarningCode.BadExpression, firstOperation.Location, "Invalid dereference operation performed on global"); expr = new Null(firstOperation.Location); } @@ -690,23 +939,20 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm // If the last operation evaluated as an ambiguous type, we force the next operation to be a search if (!fieldOperation.NoSearch && !pathIsFuzzy) { - if (prevPath == null) { - throw new UnknownIdentifierException(deref.Location, field); - } - - DMObject? fromObject = DMObjectTree.GetDMObject(prevPath.Value, false); - if (fromObject == null) - return BadExpression(WarningCode.ItemDoesntExist, fieldOperation.Location, + if (prevPath == null) + return UnknownIdentifier(deref.Location, field); + if (!ObjectTree.TryGetDMObject(prevPath.Value, out var fromObject)) + return UnknownReference(fieldOperation.Location, $"Type {prevPath.Value} does not exist"); property = fromObject.GetVariable(field); if (!fieldOperation.Safe && fromObject.IsSubtypeOf(DreamPath.Client)) { - DMCompiler.Emit(WarningCode.UnsafeClientAccess, deref.Location, + Compiler.Emit(WarningCode.UnsafeClientAccess, deref.Location, "Unsafe \"client\" access. Use the \"?.\" operator instead"); } if (property == null && fromObject.GetGlobalVariableId(field) is { } globalId) { - property = DMObjectTree.Globals[globalId]; + property = ObjectTree.Globals[globalId]; expr = new GlobalField(expr.Location, property.Type, globalId, property.ValType); @@ -715,12 +961,27 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm return expr; } - if (property == null) { - throw new UnknownIdentifierException(deref.Location, field); + if (property.ValType.IsUnimplemented) { + Compiler.UnimplementedWarning(deref.Location, + $"{prevPath}.{field} is not implemented and will have unexpected behavior"); + } + + operations = new Dereference.Operation[newOperationCount]; + astOperationOffset += i + 1; + i = -1; + prevPath = property.Type; + pathIsFuzzy = prevPath == null; + continue; + } else if (property?.CanConstFold is true && property.Value.TryAsConstant(Compiler, out var derefConst)) { + expr = derefConst; + + var newOperationCount = operations.Length - i - 1; + if (newOperationCount == 0) { + return expr; } if (property.ValType.IsUnimplemented) { - DMCompiler.UnimplementedWarning(deref.Location, + Compiler.UnimplementedWarning(deref.Location, $"{prevPath}.{field} is not implemented and will have unexpected behavior"); } @@ -733,7 +994,7 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm } if (property == null) { - throw new UnknownIdentifierException(deref.Location, field); + return UnknownIdentifier(deref.Location, field); } } @@ -752,7 +1013,7 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm operation = new Dereference.IndexOperation { // var/type1/result = new /type2()[new()] changes the inferred new to "new /type1()" // L[new()] = new() uses the type of L however - Index = DMExpression.Create(dmObject, proc, indexOperation.Index, inferredPath ?? prevPath), + Index = BuildExpression(indexOperation.Index, inferredPath ?? prevPath), Safe = indexOperation.Safe, Path = prevPath }; @@ -762,21 +1023,17 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm case DMASTDereference.CallOperation callOperation: { var field = callOperation.Identifier; - ArgumentList argumentList = new(deref.Expression.Location, dmObject, proc, callOperation.Parameters); + var argumentList = BuildArgumentList(deref.Expression.Location, callOperation.Parameters); if (!callOperation.NoSearch && !pathIsFuzzy) { if (prevPath == null) { - throw new UnknownIdentifierException(deref.Location, field); + return UnknownIdentifier(deref.Location, field); } - DMObject? fromObject = DMObjectTree.GetDMObject(prevPath.Value, false); - if (fromObject == null) - return BadExpression(WarningCode.ItemDoesntExist, callOperation.Location, - $"Type {prevPath.Value} does not exist"); - + if (!ObjectTree.TryGetDMObject(prevPath.Value, out var fromObject)) + return UnknownReference(callOperation.Location, $"Type {prevPath.Value} does not exist"); if (!fromObject.HasProc(field)) - return BadExpression(WarningCode.ItemDoesntExist, callOperation.Location, - $"Type {prevPath.Value} does not have a proc named \"{field}\""); + return UnknownIdentifier(callOperation.Location, field); } operation = new Dereference.CallOperation { @@ -798,11 +1055,11 @@ private static DMExpression BuildDereference(DMASTDereference deref, DMObject dm } // The final value in prevPath is our expression's path! - return new Dereference(deref.Location, prevPath, expr, operations); + return new Dereference(ObjectTree, deref.Location, prevPath, expr, operations); } - private static DMExpression BuildLocate(DMASTLocate locate, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var container = locate.Container != null ? DMExpression.Create(dmObject, proc, locate.Container, inferredPath) : null; + private DMExpression BuildLocate(DMASTLocate locate, DreamPath? inferredPath) { + var container = locate.Container != null ? BuildExpression(locate.Container, inferredPath) : null; if (locate.Expression == null) { if (inferredPath == null) @@ -811,12 +1068,12 @@ private static DMExpression BuildLocate(DMASTLocate locate, DMObject dmObject, D return new LocateInferred(locate.Location, inferredPath.Value, container); } - var pathExpr = DMExpression.Create(dmObject, proc, locate.Expression, inferredPath); + var pathExpr = BuildExpression(locate.Expression, inferredPath); return new Locate(locate.Location, pathExpr, container); } - private static DMExpression BuildImplicitIsType(DMASTImplicitIsType isType, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var expr = DMExpression.Create(dmObject, proc, isType.Value, inferredPath); + private DMExpression BuildImplicitIsType(DMASTImplicitIsType isType, DreamPath? inferredPath) { + var expr = BuildExpression(isType.Value, inferredPath); if (expr.Path is null) return BadExpression(WarningCode.BadExpression, isType.Location, "An inferred istype requires a type!"); @@ -824,16 +1081,16 @@ private static DMExpression BuildImplicitIsType(DMASTImplicitIsType isType, DMOb return new IsTypeInferred(isType.Location, expr, expr.Path.Value); } - private static DMExpression BuildList(DMASTList list, DMObject dmObject, DMProc proc) { - (DMExpression? Key, DMExpression Value)[] values = Array.Empty<(DMExpression?, DMExpression)>(); + private DMExpression BuildList(DMASTList list) { + (DMExpression? Key, DMExpression Value)[] values = []; if (list.Values != null) { values = new (DMExpression?, DMExpression)[list.Values.Length]; for (int i = 0; i < list.Values.Length; i++) { DMASTCallParameter value = list.Values[i]; - DMExpression? key = (value.Key != null) ? DMExpression.Create(dmObject, proc, value.Key) : null; - DMExpression listValue = DMExpression.Create(dmObject, proc, value.Value); + DMExpression? key = (value.Key != null) ? BuildExpression(value.Key) : null; + DMExpression listValue = BuildExpression(value.Value); values[i] = (key, listValue); } @@ -842,10 +1099,10 @@ private static DMExpression BuildList(DMASTList list, DMObject dmObject, DMProc return new List(list.Location, values); } - private static DMExpression BuildDimensionalList(DMASTDimensionalList list, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { + private DMExpression BuildDimensionalList(DMASTDimensionalList list, DreamPath? inferredPath) { var sizes = new DMExpression[list.Sizes.Count]; for (int i = 0; i < sizes.Length; i++) { - var sizeExpr = DMExpression.Create(dmObject, proc, list.Sizes[i], inferredPath); + var sizeExpr = BuildExpression(list.Sizes[i], inferredPath); sizes[i] = sizeExpr; } @@ -854,16 +1111,16 @@ private static DMExpression BuildDimensionalList(DMASTDimensionalList list, DMOb } // nameof(x) - private static DMExpression BuildNameof(DMASTNameof nameof, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var expr = BuildExpression(nameof.Value, dmObject, proc, inferredPath); - if (expr.GetNameof(dmObject) is { } name) { + private DMExpression BuildNameof(DMASTNameof nameof, DreamPath? inferredPath) { + var expr = BuildExpression(nameof.Value, inferredPath); + if (expr.GetNameof(ctx) is { } name) { return new String(nameof.Location, name); } return BadExpression(WarningCode.BadArgument, nameof.Location, "nameof() requires a var, proc reference, or type path"); } - private static DMExpression BuildNewList(DMASTNewList newList, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { + private DMExpression BuildNewList(DMASTNewList newList, DreamPath? inferredPath) { DMExpression[] expressions = new DMExpression[newList.Parameters.Length]; for (int i = 0; i < newList.Parameters.Length; i++) { @@ -872,13 +1129,13 @@ private static DMExpression BuildNewList(DMASTNewList newList, DMObject dmObject return BadExpression(WarningCode.InvalidArgumentKey, parameter.Location, "newlist() does not take named arguments"); - expressions[i] = DMExpression.Create(dmObject, proc, parameter.Value, inferredPath); + expressions[i] = BuildExpression(parameter.Value, inferredPath); } return new NewList(newList.Location, expressions); } - private static DMExpression BuildAddText(DMASTAddText addText, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { + private DMExpression BuildAddText(DMASTAddText addText, DreamPath? inferredPath) { if (addText.Parameters.Length < 2) return BadExpression(WarningCode.InvalidArgumentCount, addText.Location, "Invalid addtext() parameter count; expected 2 or more arguments"); @@ -886,30 +1143,30 @@ private static DMExpression BuildAddText(DMASTAddText addText, DMObject dmObject for (int i = 0; i < expArr.Length; i++) { DMASTCallParameter parameter = addText.Parameters[i]; if(parameter.Key != null) - DMCompiler.Emit(WarningCode.InvalidArgumentKey, parameter.Location, "addtext() does not take named arguments"); + Compiler.Emit(WarningCode.InvalidArgumentKey, parameter.Location, "addtext() does not take named arguments"); - expArr[i] = DMExpression.Create(dmObject,proc, parameter.Value, inferredPath); + expArr[i] = BuildExpression(parameter.Value, inferredPath); } return new AddText(addText.Location, expArr); } - private static DMExpression BuildInput(DMASTInput input, DMObject dmObject, DMProc proc) { + private DMExpression BuildInput(DMASTInput input) { DMExpression[] arguments = new DMExpression[input.Parameters.Length]; for (int i = 0; i < input.Parameters.Length; i++) { DMASTCallParameter parameter = input.Parameters[i]; if (parameter.Key != null) { - DMCompiler.Emit(WarningCode.InvalidArgumentKey, parameter.Location, + Compiler.Emit(WarningCode.InvalidArgumentKey, parameter.Location, "input() does not take named arguments"); } - arguments[i] = DMExpression.Create(dmObject, proc, parameter.Value); + arguments[i] = BuildExpression(parameter.Value); } DMExpression? list = null; if (input.List != null) { - list = DMExpression.Create(dmObject, proc, input.List); + list = BuildExpression(input.List); DMValueType objectTypes = DMValueType.Null |DMValueType.Obj | DMValueType.Mob | DMValueType.Turf | DMValueType.Area; @@ -917,7 +1174,7 @@ private static DMExpression BuildInput(DMASTInput input, DMObject dmObject, DMPr // Default filter is "as anything" when there's a list input.Types ??= DMValueType.Anything; if (input.Types != DMValueType.Anything && (input.Types & objectTypes) == 0x0) { - DMCompiler.Emit(WarningCode.BadArgument, input.Location, + Compiler.Emit(WarningCode.BadArgument, input.Location, $"Invalid input() filter \"{input.Types}\". Filter must be \"{DMValueType.Anything}\" or at least one of \"{objectTypes}\""); } } else { @@ -931,13 +1188,13 @@ private static DMExpression BuildInput(DMASTInput input, DMObject dmObject, DMPr return new Input(input.Location, arguments, input.Types.Value, list); } - private static DMExpression BuildPick(DMASTPick pick, DMObject dmObject, DMProc proc) { + private DMExpression BuildPick(DMASTPick pick) { Pick.PickValue[] pickValues = new Pick.PickValue[pick.Values.Length]; for (int i = 0; i < pickValues.Length; i++) { DMASTPick.PickValue pickValue = pick.Values[i]; - DMExpression? weight = (pickValue.Weight != null) ? DMExpression.Create(dmObject, proc, pickValue.Weight) : null; - DMExpression value = DMExpression.Create(dmObject, proc, pickValue.Value); + DMExpression? weight = (pickValue.Weight != null) ? BuildExpression(pickValue.Weight) : null; + DMExpression value = BuildExpression(pickValue.Value); if (weight is Prob prob) // pick(prob(50);x, prob(200);y) format weight = prob.P; @@ -948,45 +1205,60 @@ private static DMExpression BuildPick(DMASTPick pick, DMObject dmObject, DMProc return new Pick(pick.Location, pickValues); } - private static DMExpression BuildLog(DMASTLog log, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var expr = DMExpression.Create(dmObject, proc, log.Expression, inferredPath); + private DMExpression BuildLog(DMASTLog log, DreamPath? inferredPath) { + var expr = BuildExpression(log.Expression, inferredPath); DMExpression? baseExpr = null; if (log.BaseExpression != null) { - baseExpr = DMExpression.Create(dmObject, proc, log.BaseExpression, inferredPath); + baseExpr = BuildExpression(log.BaseExpression, inferredPath); } return new Log(log.Location, expr, baseExpr); } - private static DMExpression BuildCall(DMASTCall call, DMObject dmObject, DMProc proc, DreamPath? inferredPath) { - var procArgs = new ArgumentList(call.Location, dmObject, proc, call.ProcParameters, inferredPath); + private DMExpression BuildCall(DMASTCall call, DreamPath? inferredPath) { + var procArgs = BuildArgumentList(call.Location, call.ProcParameters, inferredPath); switch (call.CallParameters.Length) { default: - DMCompiler.Emit(WarningCode.InvalidArgumentCount, call.Location, "Too many arguments for call()"); + Compiler.Emit(WarningCode.InvalidArgumentCount, call.Location, "Too many arguments for call()"); goto case 2; // Fallthrough! case 2: { - var a = DMExpression.Create(dmObject, proc, call.CallParameters[0].Value, inferredPath); - var b = DMExpression.Create(dmObject, proc, call.CallParameters[1].Value, inferredPath); + var a = BuildExpression(call.CallParameters[0].Value, inferredPath); + var b = BuildExpression(call.CallParameters[1].Value, inferredPath); return new CallStatement(call.Location, a, b, procArgs); } case 1: { - var a = DMExpression.Create(dmObject, proc, call.CallParameters[0].Value, inferredPath); + var a = BuildExpression(call.CallParameters[0].Value, inferredPath); return new CallStatement(call.Location, a, procArgs); } case 0: - DMCompiler.Emit(WarningCode.InvalidArgumentCount, call.Location, "Not enough arguments for call()"); + Compiler.Emit(WarningCode.InvalidArgumentCount, call.Location, "Not enough arguments for call()"); return new CallStatement(call.Location, new Null(Location.Internal), procArgs); } } /// /// Emits an error and returns a
- /// Common pattern, so here's a one-line helper ///
- private static DMExpression BadExpression(WarningCode code, Location location, string errorMessage) { - DMCompiler.Emit(code, location, errorMessage); + private BadExpression BadExpression(WarningCode code, Location location, string errorMessage) { + if (_encounteredUnknownReference == null) + Compiler.Emit(code, location, errorMessage); return new BadExpression(location); } + + /// + /// Creates an UnknownReference expression that should be returned at the end of the expression building.
+ /// Always use this to return an UnknownReference! + ///
+ private UnknownReference UnknownReference(Location location, string errorMessage) { + _encounteredUnknownReference = new UnknownReference(location, errorMessage); + return _encounteredUnknownReference; + } + + /// + /// but with a common message + /// + private UnknownReference UnknownIdentifier(Location location, string identifier) => + UnknownReference(location, $"Unknown identifier \"{identifier}\""); } diff --git a/DMCompiler/DM/Builders/DMObjectBuilder.cs b/DMCompiler/DM/Builders/DMObjectBuilder.cs deleted file mode 100644 index 78984020ff..0000000000 --- a/DMCompiler/DM/Builders/DMObjectBuilder.cs +++ /dev/null @@ -1,551 +0,0 @@ -using DMCompiler.Compiler; -using DMCompiler.Compiler.DM.AST; -using DMCompiler.DM.Expressions; - -namespace DMCompiler.DM.Builders; - -internal static class DMObjectBuilder { - private static readonly List<(DMObject, DMASTObjectVarDefinition)> VarDefinitions = new(); - private static readonly List<(DMObject, DMASTObjectVarOverride)> VarOverrides = new(); - private static readonly List<(DMObject?, DMASTProcDefinition)> ProcDefinitions = new(); - private static readonly List<(DMObject DMObject, DMASTObjectVarDefinition VarDecl)> StaticObjectVars = new(); - private static readonly List<(DMObject DMObject, DMProc Proc, int Id, DMASTProcStatementVarDeclaration VarDecl)> StaticProcVars = new(); - - private static int _firstProcGlobal = -1; - - public static void Reset() { - DMObjectTree.Reset(); // Blank the object tree - DMExpressionBuilder.ScopeOperatorEnabled = false; - - VarDefinitions.Clear(); - VarOverrides.Clear(); - ProcDefinitions.Clear(); - StaticObjectVars.Clear(); - StaticProcVars.Clear(); - - _firstProcGlobal = -1; - } - - public static void BuildObjectTree(DMASTFile astFile) { - Reset(); - - // Step 1: Define every type in the code. Collect proc and var declarations for later. - // Also handles parent_type - ProcessFile(astFile); - - // Step 2: Define every proc and proc override (but do not compile!) - // Collect static vars inside procs for later. - foreach (var procDef in ProcDefinitions) { - ProcessProcDefinition(procDef.Item2, procDef.Item1); - } - - // Step 3: Create static vars - List<(DMObject, DMASTObjectVarDefinition, UnknownIdentifierException e)> lateVarDefs = new(); - List<(DMObject, DMProc, DMASTProcStatementVarDeclaration, int, UnknownIdentifierException e)> lateProcVarDefs = new(); - for (int i = 0; i <= StaticObjectVars.Count; i++) { - // Static vars are initialized in code-order, except proc statics are all lumped together - if (i == _firstProcGlobal) { - foreach (var procStatic in StaticProcVars) { - if (procStatic.VarDecl.Value == null) - continue; - - try { - DMExpressionBuilder.CurrentScopeMode = DMExpressionBuilder.ScopeMode.FirstPassStatic; - DMExpression expression = DMExpression.Create(procStatic.DMObject, procStatic.Proc, - procStatic.VarDecl.Value, procStatic.VarDecl.Type); - - DMObjectTree.AddGlobalInitAssign(procStatic.Id, expression); - } catch (UnknownIdentifierException e) { - // For step 6 - lateProcVarDefs.Add((procStatic.DMObject, procStatic.Proc, procStatic.VarDecl, procStatic.Id, e)); - } finally { - DMExpressionBuilder.CurrentScopeMode = DMExpressionBuilder.ScopeMode.Normal; - } - } - } - - if (i == StaticObjectVars.Count) - break; - - var objectStatic = StaticObjectVars[i]; - - try { - ProcessVarDefinition(objectStatic.DMObject, objectStatic.VarDecl); - } catch (UnknownIdentifierException e) { - lateVarDefs.Add((objectStatic.DMObject, objectStatic.VarDecl, e)); // For step 6 - } - } - - // Step 4: Define non-static vars - foreach (var varDef in VarDefinitions) { - try { - ProcessVarDefinition(varDef.Item1, varDef.Item2); - } catch (UnknownIdentifierException e) { - lateVarDefs.Add((varDef.Item1, varDef.Item2, e)); // For step 6 - } - } - - // Step 5: Apply var overrides - List<(DMObject, DMASTObjectVarOverride, UnknownIdentifierException e)> lateOverrides = new(); - foreach (var varOverride in VarOverrides) { - try { - ProcessVarOverride(varOverride.Item1, varOverride.Item2); - } catch (UnknownIdentifierException e) { - lateOverrides.Add((varOverride.Item1, varOverride.Item2, e)); // For step 7 - } - } - - // Step 6: Attempt to resolve all vars that referenced other not-yet-existing or overridden vars - DMExpressionBuilder.ScopeOperatorEnabled = true; - ProcessLateVarDefs(lateVarDefs, lateProcVarDefs, lateOverrides); - - // The vars these reference were never found, emit their errors - foreach (var lateVarDef in lateVarDefs) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, lateVarDef.Item3.Location, - $"Unknown identifier \"{lateVarDef.Item3.Identifier}\""); - } - - foreach (var lateVarDef in lateProcVarDefs) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, lateVarDef.Item3.Location, - $"Unknown identifier \"{lateVarDef.Item5.Identifier}\""); - } - - // Step 7: Create each types' initialization proc (initializes vars that aren't constants) - foreach (DMObject dmObject in DMObjectTree.AllObjects) { - dmObject.CreateInitializationProc(); - } - - // Step 8: Compile every proc - foreach (DMProc proc in DMObjectTree.AllProcs) - proc.Compile(); - - // Step 9: Create & Compile the global init proc (initializes global vars) - DMObjectTree.CreateGlobalInitProc(); - } - - private static void ProcessFile(DMASTFile file) { - ProcessBlockInner(file.BlockInner, DMObjectTree.Root); - } - - private static void ProcessBlockInner(DMASTBlockInner blockInner, DMObject? currentObject) { - foreach (DMASTStatement statement in blockInner.Statements) { - ProcessStatement(statement, currentObject); - } - } - - public static void ProcessStatement(DMASTStatement statement, DMObject? currentObject = null) { - switch (statement) { - case DMASTObjectDefinition objectDefinition: - ProcessObjectDefinition(objectDefinition); - break; - - case DMASTObjectVarDefinition varDefinition: - var dmObject = DMObjectTree.GetDMObject(varDefinition.ObjectPath)!; - - if (varDefinition.IsGlobal) { - // var/static/list/L[1][2][3] and list() both come first in global init order - if (varDefinition.Value is DMASTDimensionalList || - (varDefinition.Value is DMASTList list && list.AllValuesConstant())) - StaticObjectVars.Insert(0, (dmObject, varDefinition)); - else - StaticObjectVars.Add((dmObject, varDefinition)); - } else { - VarDefinitions.Add((dmObject, varDefinition)); - } - - break; - case DMASTObjectVarOverride varOverride: - // parent_type is treated as part of the object definition rather than an actual var override - if (varOverride.VarName == "parent_type") { - if (varOverride.Value is not DMASTConstantPath parentTypePath) { - DMCompiler.Emit(WarningCode.BadExpression, varOverride.Location, "Expected a constant path"); - break; // Ignore it - } - - var parentType = DMObjectTree.GetDMObject(parentTypePath.Value.Path); - - DMObjectTree.GetDMObject(varOverride.ObjectPath)!.Parent = parentType; - break; - } - - VarOverrides.Add((DMObjectTree.GetDMObject(varOverride.ObjectPath)!, varOverride)); - break; - case DMASTProcDefinition procDefinition: - if (procDefinition.Body != null) { - foreach (var stmt in GetStatements(procDefinition.Body)) { - // TODO multiple var definitions. - if (stmt is DMASTProcStatementVarDeclaration { IsGlobal: true }) { - if (_firstProcGlobal == -1) - _firstProcGlobal = StaticObjectVars.Count; - break; - } - } - } - - ProcDefinitions.Add((currentObject, procDefinition)); - break; - case DMASTMultipleObjectVarDefinitions multipleVarDefinitions: { - foreach (DMASTObjectVarDefinition varDefinition in multipleVarDefinitions.VarDefinitions) { - VarDefinitions.Add((DMObjectTree.GetDMObject(varDefinition.ObjectPath)!, varDefinition)); - } - - break; - } - default: - DMCompiler.ForcedError(statement.Location, $"Invalid object statement {statement.GetType()}"); - break; - } - } - - private static void ProcessObjectDefinition(DMASTObjectDefinition objectDefinition) { - DMCompiler.VerbosePrint($"Generating {objectDefinition.Path}"); - - DMObject? newCurrentObject = DMObjectTree.GetDMObject(objectDefinition.Path); - if (objectDefinition.InnerBlock != null) ProcessBlockInner(objectDefinition.InnerBlock, newCurrentObject); - } - - private static void ProcessVarDefinition(DMObject? varObject, DMASTObjectVarDefinition? varDefinition) { - DMVariable? variable = null; - - //DMObjects store two bundles of variables; the statics in GlobalVariables and the non-statics in Variables. - //Lets check if we're duplicating a definition, first. - if (varObject.HasGlobalVariable(varDefinition.Name)) { - DMCompiler.Emit(WarningCode.DuplicateVariable, varDefinition.Location, $"Duplicate definition of static var \"{varDefinition.Name}\""); - variable = varObject.GetGlobalVariable(varDefinition.Name); - } else if (varObject.HasLocalVariable(varDefinition.Name)) { - if(!DoesDefineSnowflakeVars(varDefinition, varObject)) - DMCompiler.Emit(WarningCode.DuplicateVariable, varDefinition.Location, $"Duplicate definition of var \"{varDefinition.Name}\""); - variable = varObject.GetVariable(varDefinition.Name); - } else if (varDefinition.IsStatic && DoesOverrideGlobalVars(varDefinition)) { // static TODO: Fix this else-if chaining once _currentObject is refactored out of DMObjectBuilder. - DMCompiler.Emit(WarningCode.DuplicateVariable, varDefinition.Location, "Duplicate definition of global.vars"); - //We can't salvage any part of this definition, since global.vars doesn't technically even exist, so lets just return - return; - } - - DMExpression expression; - try { - if (varDefinition.IsGlobal) - DMExpressionBuilder.CurrentScopeMode = DMExpressionBuilder.ScopeMode.Static; // FirstPassStatic is not used for object vars - - // TODO: no, bad. instance field declarations should have a proc assigned to them. - expression = DMExpression.Create(varObject, varDefinition.IsGlobal ? DMObjectTree.GlobalInitProc : null, - varDefinition.Value, varDefinition.Type); - } finally { - DMExpressionBuilder.CurrentScopeMode = DMExpressionBuilder.ScopeMode.Normal; - } - - if (variable is null) { - if (varDefinition.IsStatic) { - variable = varObject.CreateGlobalVariable(varDefinition.Type, varDefinition.Name, varDefinition.IsConst, varDefinition.ValType); - } else { - variable = new DMVariable(varDefinition.Type, varDefinition.Name, false, varDefinition.IsConst, varDefinition.IsTmp, varDefinition.ValType); - varObject.Variables[variable.Name] = variable; - if(varDefinition.IsConst){ - varObject.ConstVariables ??= new HashSet(); - varObject.ConstVariables.Add(varDefinition.Name); - } - if(varDefinition.IsTmp){ - varObject.TmpVariables ??= new HashSet(); - varObject.TmpVariables.Add(varDefinition.Name); - } - } - } - - // TODO: why are we passing the variable ref? we aren't using it after this - SetVariableValue(varObject, ref variable, varDefinition.Location, expression); - } - - private static void ProcessVarOverride(DMObject? varObject, DMASTObjectVarOverride? varOverride) { - switch (varOverride.VarName) { - // Keep in mind that anything here, by default, affects all objects, even those who don't inherit from /datum - case "tag": { - if (varObject.IsSubtypeOf(DreamPath.Datum)) { - DMCompiler.Emit(WarningCode.BadExpression, varOverride.Location, - "var \"tag\" cannot be set to a value at compile-time"); - } - - break; - } - } - - DMVariable? variable; - if (varObject.HasLocalVariable(varOverride.VarName)) { - variable = varObject.GetVariable(varOverride.VarName); - } else if (varObject.HasGlobalVariable(varOverride.VarName)) { - variable = varObject.GetGlobalVariable(varOverride.VarName); - DMCompiler.Emit(WarningCode.StaticOverride, varOverride.Location, $"var \"{varOverride.VarName}\" cannot be overridden - it is a global var"); - } else { - throw new UnknownIdentifierException(varOverride.Location, varOverride.VarName); - } - - OverrideVariableValue(varObject, ref variable, varOverride.Value); - varObject.VariableOverrides[variable.Name] = variable; - } - - private static void ProcessProcDefinition(DMASTProcDefinition procDefinition, DMObject? currentObject) { - string procName = procDefinition.Name; - DMObject dmObject = DMObjectTree.GetDMObject(currentObject.Path.Combine(procDefinition.ObjectPath)); - bool hasProc = dmObject.HasProc(procName); // Trying to avoid calling this several times since it's recursive and maybe slow - if (!procDefinition.IsOverride && hasProc) { // If this is a define and we already had a proc somehow - if(!dmObject.HasProcNoInheritance(procName)) { // If we're inheriting this proc (so making a new define for it at our level is stupid) - DMCompiler.Emit(WarningCode.DuplicateProcDefinition, procDefinition.Location, $"Type {dmObject.Path} already inherits a proc named \"{procName}\" and cannot redefine it"); - return; // TODO: Maybe fallthrough since this error is a little pedantic? - } - //Otherwise, it's ok - } - - DMProc proc = DMObjectTree.CreateDMProc(dmObject, procDefinition); - proc.IsVerb = procDefinition.IsVerb; - - if (procDefinition.ObjectPath == DreamPath.Root) { - if(procDefinition.IsOverride) { - DMCompiler.Emit(WarningCode.InvalidOverride, procDefinition.Location, $"Global procs cannot be overridden - '{procDefinition.Name}' override will be ignored"); - //Continue processing the proc anyhoo, just don't add it. - } else { - if (!DMObjectTree.SeenGlobalProcDefinition.Add(procName)) { // Add() is equivalent to Dictionary's TryAdd() for some reason - DMCompiler.Emit(WarningCode.DuplicateProcDefinition, procDefinition.Location, $"Global proc {procDefinition.Name} is already defined"); - //Again, even though this is likely an error, process the statements anyways. - } else { - DMObjectTree.AddGlobalProc(proc); - } - } - } else { - dmObject.AddProc(procName, proc); - } - - if (procDefinition.Body != null) { - foreach (var stmt in GetStatements(procDefinition.Body)) { - // TODO multiple var definitions. - if (stmt is DMASTProcStatementVarDeclaration varDeclaration && varDeclaration.IsGlobal) { - DMVariable variable = proc.CreateGlobalVariable(varDeclaration.Type, varDeclaration.Name, varDeclaration.IsConst, out var globalId); - variable.Value = new Expressions.Null(varDeclaration.Location); - - StaticProcVars.Add((dmObject, proc, globalId, varDeclaration)); - } - } - } - - if (procDefinition.IsVerb && (dmObject.IsSubtypeOf(DreamPath.Atom) || dmObject.IsSubtypeOf(DreamPath.Client)) && !DMCompiler.Settings.NoStandard) { - dmObject.AddVerb(proc); - } - } - - // TODO: Remove this entirely - public static IEnumerable GetStatements(DMASTProcBlockInner block) { - foreach (var stmt in block.Statements) { - yield return stmt; - List recurse; - switch (stmt) { - case DMASTProcStatementSpawn ps: recurse = new() { ps.Body }; break; - case DMASTProcStatementIf ps: recurse = new() { ps.Body, ps.ElseBody }; break; - case DMASTProcStatementFor ps: recurse = new() { ps.Body }; break; - case DMASTProcStatementWhile ps: recurse = new() { ps.Body }; break; - case DMASTProcStatementDoWhile ps: recurse = new() { ps.Body }; break; - case DMASTProcStatementInfLoop ps: recurse = new() { ps.Body }; break; - // TODO Good luck if you declare a static var inside a switch - case DMASTProcStatementSwitch ps: { - recurse = new(); - foreach (var swcase in ps.Cases) { - recurse.Add(swcase.Body); - } - break; - } - case DMASTProcStatementTryCatch ps: recurse = new() { ps.TryBody, ps.CatchBody }; break; - default: recurse = new(); break; - } - - foreach (var subblock in recurse) { - if (subblock == null) { continue; } - foreach (var substmt in GetStatements(subblock)) { - yield return substmt; - } - } - } - - } - - /// - /// A snowflake helper proc which determines whether the given definition would be a duplication definition of global.vars.
- /// It exists because global.vars is not a "real" global but rather a construct indirectly implemented via PushGlobals et al. - ///
- private static bool DoesOverrideGlobalVars(DMASTObjectVarDefinition varDefinition) { - return varDefinition.IsStatic && varDefinition.Name == "vars" && varDefinition.ObjectPath == DreamPath.Root; - } - - /// - /// A snowflake helper proc which allows for ignoring variable duplication in the specific case that /world or /client are inheriting from /datum,
- /// which would normally throw an error since all of these classes have their own var/vars definition. - ///
- private static bool DoesDefineSnowflakeVars(DMASTObjectVarDefinition varDefinition, DMObject varObject) { - if (DMCompiler.Settings.NoStandard == false) - if (varDefinition.Name == "vars") - if (varDefinition.ObjectPath == DreamPath.World || varDefinition.ObjectPath == DreamPath.Client) - if (varObject.IsSubtypeOf(DreamPath.Datum)) - return true; - return false; - } - - /// - /// A filter proc above
- /// which checks first to see if overriding this thing's value is valid (as in the case of const and ) - ///
- private static void OverrideVariableValue(DMObject currentObject, ref DMVariable variable, - DMASTExpression value) { - if (variable.IsConst) { - DMCompiler.Emit(WarningCode.WriteToConstant, value.Location, - $"Var {variable.Name} is const and cannot be modified"); - return; - } - - if (variable.ValType.IsCompileTimeReadOnly) { - DMCompiler.Emit(WarningCode.WriteToConstant, value.Location, - $"Var {variable.Name} is a native read-only value which cannot be modified"); - } - - try { - if (variable.IsGlobal) - DMExpressionBuilder.CurrentScopeMode = DMExpressionBuilder.ScopeMode.Static; - - DMExpression expression = DMExpression.Create(currentObject, variable.IsGlobal ? DMObjectTree.GlobalInitProc : null, value, variable.Type); - - SetVariableValue(currentObject, ref variable, value.Location, expression, true); - } finally { - DMExpressionBuilder.CurrentScopeMode = DMExpressionBuilder.ScopeMode.Normal; - } - } - - private static void SetVariableValue(DMObject currentObject, ref DMVariable variable, Location location, DMExpression expression, bool isOverride = false) { - // Typechecking - if (!variable.ValType.MatchesType(expression.ValType) && !variable.ValType.IsUnimplemented) { - if (expression is Null && !isOverride) { - DMCompiler.Emit(WarningCode.ImplicitNullType, expression.Location, $"{currentObject.Path.ToString()}.{variable.Name}: Variable is null but not explicitly typed as nullable, append \"|null\" to \"as\". Implicitly treating as nullable."); - variable.ValType |= DMValueType.Null; - } else { - DMCompiler.Emit(WarningCode.InvalidVarType, expression.Location, $"{currentObject.Path.ToString()}.{variable.Name}: Invalid var value type {expression.ValType}, expected {variable.ValType}"); - } - } - - if (expression.TryAsConstant(out var constant)) { - variable = variable.WriteToValue(constant); - return; - } - - if (variable.IsConst) { - DMCompiler.Emit(WarningCode.HardConstContext, location, "Value of const var must be a constant"); - return; - } - - if (!IsValidRighthandSide(currentObject, variable, expression)) { - DMCompiler.Emit(WarningCode.BadExpression, - location, - $"Invalid initial value for \"{variable.Name}\""); - return; - } - - variable = variable.WriteToValue(new Expressions.Null(Location.Internal)); - EmitInitializationAssign(currentObject, variable, expression); - } - - /// This expression should have already been processed by TryAsConstant. - /// true if the expression given can be used to initialize the given variable. false if not. - private static bool IsValidRighthandSide(DMObject currentObject, DMVariable variable, DMExpression expression) { - if (variable.IsGlobal) // Have to back out early like this because if we are a static set by a ProcCall, it might be underdefined right now (and so error in the switch) - return true; - - return expression switch { - //TODO: A better way of handling procs evaluated at compile time - Expressions.ProcCall procCall => procCall.GetTargetProc(currentObject).Proc?.Name switch { - "generator" => true, - "matrix" => true, - "icon" => true, - "file" => true, - "sound" => true, - "nameof" => true, - _ => false - }, - - Expressions.List => true, - Expressions.DimensionalList => true, - Expressions.NewList => true, - Expressions.NewPath => true, - Expressions.Rgb => true, - // TODO: Check for circular reference loops here - // (Note that we do accidentally support global-field access somewhat when it gets const-folded by TryAsConstant before we get here) - Expressions.GlobalField => false, - _ => false - }; - } - - private static void EmitInitializationAssign(DMObject currentObject, DMVariable variable, DMExpression expression) { - if (variable.IsGlobal) { - int? globalId = currentObject.GetGlobalVariableId(variable.Name); - if (globalId == null) { - DMCompiler.Emit(WarningCode.BadExpression, expression?.Location ?? Location.Unknown, - $"Invalid global {currentObject.Path}.{variable.Name}"); - return; - } - - DMObjectTree.AddGlobalInitAssign(globalId.Value, expression); - } else { - var initLoc = expression.Location; - var field = new Field(initLoc, variable, variable.ValType); - var assign = new Assignment(initLoc, field, expression); - - currentObject.InitializationProcExpressions.Add(assign); - } - } - - private static void ProcessLateVarDefs(List<(DMObject, DMASTObjectVarDefinition, UnknownIdentifierException e)> lateVarDefs, List<(DMObject, DMProc, DMASTProcStatementVarDeclaration, int, UnknownIdentifierException e)> lateProcVarDefs, List<(DMObject, DMASTObjectVarOverride, UnknownIdentifierException e)> lateOverrides) { - int lastLateVarDefCount; - do { - lastLateVarDefCount = lateVarDefs.Count + lateProcVarDefs.Count + lateOverrides.Count; - - // Vars outside of procs - for (int i = 0; i < lateVarDefs.Count; i++) { - var varDef = lateVarDefs[i]; - - try { - ProcessVarDefinition(varDef.Item1, varDef.Item2); - - // Success! Remove this one from the list - lateVarDefs.RemoveAt(i--); - } catch (UnknownIdentifierException) { - // Keep it in the list, try again after the rest have been processed - } - } - - // Static vars inside procs - for (int i = 0; i < lateProcVarDefs.Count; i++) { - var varDef = lateProcVarDefs[i]; - var varDecl = varDef.Item3; - - try { - DMExpressionBuilder.CurrentScopeMode = DMExpressionBuilder.ScopeMode.Static; - DMExpression expression = - DMExpression.Create(varDef.Item1, varDef.Item2, varDecl.Value!, varDecl.Type); - - DMObjectTree.AddGlobalInitAssign(varDef.Item4, expression); - - // Success! Remove this one from the list - lateProcVarDefs.RemoveAt(i--); - } catch (UnknownIdentifierException) { - // Keep it in the list, try again after the rest have been processed - } finally { - DMExpressionBuilder.CurrentScopeMode = DMExpressionBuilder.ScopeMode.Normal; - } - } - - // Overrides - for (int i = 0; i < lateOverrides.Count; i++) { - try { - ProcessVarOverride(lateOverrides[i].Item1, lateOverrides[i].Item2); - - // Success! Remove this one from the list - lateOverrides.RemoveAt(i--); - } catch (UnknownIdentifierException) { - // Keep it in the list, try again after the rest have been processed - } - } - } while ((lateVarDefs.Count + lateProcVarDefs.Count + lateOverrides.Count) != lastLateVarDefCount); // As long as the lists are getting smaller, keep trying - } -} diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index 01111ce837..e4fcd52a2d 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -6,7 +6,9 @@ using DMCompiler.DM.Expressions; namespace DMCompiler.DM.Builders { - internal sealed class DMProcBuilder(DMObject dmObject, DMProc proc) { + internal sealed class DMProcBuilder(DMCompiler compiler, DMObject dmObject, DMProc proc) { + private readonly DMExpressionBuilder _exprBuilder = new(new(compiler, dmObject, proc)); + /// /// BYOND currently has a ridiculous behaviour, where,
/// sometimes when a set statement has a right-hand side that is non-constant,
@@ -17,6 +19,8 @@ internal sealed class DMProcBuilder(DMObject dmObject, DMProc proc) { // Starts null; marks that we've never seen one before and should just error like normal people. private Constant? _previousSetStatementValue; + private ExpressionContext ExprContext => new(compiler, dmObject, proc); + public void ProcessProcDefinition(DMASTProcDefinition procDefinition) { if (procDefinition.Body == null) return; @@ -33,7 +37,7 @@ public void ProcessProcDefinition(DMASTProcDefinition procDefinition) { proc.JumpIfFalse(afterDefaultValueCheck); //Set default - DMExpression.Emit(dmObject, proc, parameter.Value, parameter.ObjectType); + _exprBuilder.Emit(parameter.Value, parameter.ObjectType); proc.Assign(parameterRef); proc.Pop(); @@ -55,20 +59,16 @@ private void ProcessBlockInner(DMASTProcBlockInner block, bool silenceEmptyBlock foreach (var stmt in block.SetStatements) { Debug.Assert(stmt.IsAggregateOr(), "Non-set statements were located in the block's SetStatements array! This is a bug."); - try { - ProcessStatement(stmt); - } catch (UnknownIdentifierException e) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, e.Location, $"Unknown identifier \"{e.Identifier}\""); - } + ProcessStatement(stmt); } if(!silenceEmptyBlockWarning && block.Statements.Length == 0) { // 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"); + compiler.Emit(WarningCode.EmptyBlock,block.Location,"Empty block detected - set statements are executed outside of, before, and unconditional to, this block"); } else { - DMCompiler.Emit(WarningCode.EmptyBlock,block.Location,"Empty block detected"); + compiler.Emit(WarningCode.EmptyBlock,block.Location,"Empty block detected"); } return; @@ -76,12 +76,7 @@ private void ProcessBlockInner(DMASTProcBlockInner block, bool silenceEmptyBlock foreach (DMASTProcStatement statement in block.Statements) { proc.DebugSource(statement.Location); - - try { - ProcessStatement(statement); - } catch (UnknownIdentifierException e) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, e.Location, $"Unknown identifier \"{e.Identifier}\""); - } + ProcessStatement(statement); } } @@ -124,13 +119,13 @@ public void ProcessStatement(DMASTProcStatement statement) { ProcessStatementVarDeclaration(declare); break; default: - DMCompiler.ForcedError(statement.Location, $"Invalid proc statement {statement.GetType()}"); + compiler.ForcedError(statement.Location, $"Invalid proc statement {statement.GetType()}"); break; } } public void ProcessStatementExpression(DMASTProcStatementExpression statement) { - DMExpression.Emit(dmObject, proc, statement.Expression); + _exprBuilder.Emit(statement.Expression); proc.Pop(); } @@ -179,11 +174,11 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { proc.VerbSrc = VerbSrc.InUsr; } else if (deref == "loc") { proc.VerbSrc = VerbSrc.UsrLoc; - DMCompiler.UnimplementedWarning(statementSet.Location, + compiler.UnimplementedWarning(statementSet.Location, "'set src = usr.loc' is unimplemented"); } else if (deref == "group") { proc.VerbSrc = VerbSrc.UsrGroup; - DMCompiler.UnimplementedWarning(statementSet.Location, + compiler.UnimplementedWarning(statementSet.Location, "'set src = usr.group' is unimplemented"); } else { goto default; @@ -193,10 +188,10 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { case DMASTIdentifier {Identifier: "world"}: proc.VerbSrc = statementSet.WasInKeyword ? VerbSrc.InWorld : VerbSrc.World; if (statementSet.WasInKeyword) - DMCompiler.UnimplementedWarning(statementSet.Location, + compiler.UnimplementedWarning(statementSet.Location, "'set src = world.contents' is unimplemented"); else - DMCompiler.UnimplementedWarning(statementSet.Location, + compiler.UnimplementedWarning(statementSet.Location, "'set src = world' is unimplemented"); break; case DMASTDereference {Expression: DMASTIdentifier{Identifier: "world"}, Operations: var operations}: @@ -204,7 +199,7 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { goto default; proc.VerbSrc = VerbSrc.InWorld; - DMCompiler.UnimplementedWarning(statementSet.Location, + compiler.UnimplementedWarning(statementSet.Location, "'set src = world.contents' is unimplemented"); break; case DMASTProcCall {Callable: DMASTCallableProcIdentifier {Identifier: { } viewType and ("view" or "oview")}}: @@ -223,15 +218,15 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { proc.VerbSrc = viewType == "range" ? VerbSrc.Range : VerbSrc.ORange; break; default: - DMCompiler.Emit(WarningCode.BadExpression, statementSet.Value.Location, "Invalid verb src"); + compiler.Emit(WarningCode.BadExpression, statementSet.Value.Location, "Invalid verb src"); break; } return; } - if (!DMExpression.TryConstant(dmObject, proc, statementSet.Value, out var constant)) { // If this set statement's rhs is not constant - bool didError = DMCompiler.Emit(WarningCode.InvalidSetStatement, statementSet.Location, $"'{attribute}' attribute should be a constant"); + if (!_exprBuilder.TryConstant(statementSet.Value, out var constant)) { // If this set statement's rhs is not constant + bool didError = compiler.Emit(WarningCode.InvalidSetStatement, statementSet.Location, $"'{attribute}' attribute should be a constant"); if (didError) // if this is an error return; // don't do the cursed thing @@ -242,14 +237,14 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { // oh no. if (constant is null) { - DMCompiler.Emit(WarningCode.BadExpression, statementSet.Location, $"'{attribute}' attribute must be a constant"); + compiler.Emit(WarningCode.BadExpression, statementSet.Location, $"'{attribute}' attribute must be a constant"); return; } // Check if it was 'set x in y' or whatever // (which is illegal for everything except setting src to something) if (statementSet.WasInKeyword) { - DMCompiler.Emit(WarningCode.BadToken, statementSet.Location, "Use of 'in' keyword is illegal here. Did you mean '='?"); + compiler.Emit(WarningCode.BadToken, statementSet.Location, "Use of 'in' keyword is illegal here. Did you mean '='?"); //fallthrough into normal behaviour because this error is kinda pedantic } @@ -283,7 +278,7 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { else proc.Attributes &= ~ProcAttributes.Instant; - DMCompiler.UnimplementedWarning(statementSet.Location, "set instant is not implemented"); + compiler.UnimplementedWarning(statementSet.Location, "set instant is not implemented"); break; case "background": if (constant.IsTruthy()) @@ -293,7 +288,7 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { break; case "name": if (constant is not Expressions.String nameStr) { - DMCompiler.Emit(WarningCode.BadExpression, constant.Location, "name attribute must be a string"); + compiler.Emit(WarningCode.BadExpression, constant.Location, "name attribute must be a string"); break; } @@ -305,7 +300,7 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { } else if (constant is Null) { proc.VerbCategory = null; } else { - DMCompiler.Emit(WarningCode.BadExpression, constant.Location, + compiler.Emit(WarningCode.BadExpression, constant.Location, "category attribute must be a string or null"); } @@ -313,7 +308,7 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { case "desc": // TODO: verb.desc is supposed to be printed when you type the verb name and press F1. Check the ref for details. if (constant is not Expressions.String descStr) { - DMCompiler.Emit(WarningCode.BadExpression, constant.Location, "desc attribute must be a string"); + compiler.Emit(WarningCode.BadExpression, constant.Location, "desc attribute must be a string"); break; } @@ -323,25 +318,25 @@ public void ProcessStatementSet(DMASTProcStatementSet statementSet) { // The ref says 0-101 for atoms and 0-100 for verbs // BYOND doesn't clamp the actual var value but it does seem to treat out-of-range values as their extreme if (constant is not Number invisNum) { - DMCompiler.Emit(WarningCode.BadExpression, constant.Location, "invisibility attribute must be an int"); + compiler.Emit(WarningCode.BadExpression, constant.Location, "invisibility attribute must be an int"); break; } proc.Invisibility = Convert.ToSByte(Math.Clamp(MathF.Floor(invisNum.Value), 0f, 100f)); break; case "src": - DMCompiler.UnimplementedWarning(statementSet.Location, "set src is not implemented"); + compiler.UnimplementedWarning(statementSet.Location, "set src is not implemented"); break; } } public void ProcessStatementDel(DMASTProcStatementDel statementDel) { - DMExpression.Emit(dmObject, proc, statementDel.Value); + _exprBuilder.Emit(statementDel.Value); proc.DeleteObject(); } public void ProcessStatementSpawn(DMASTProcStatementSpawn statementSpawn) { - DMExpression.Emit(dmObject, proc, statementSpawn.Delay); + _exprBuilder.Emit(statementSpawn.Delay); string afterSpawnLabel = proc.NewLabelName(); proc.Spawn(afterSpawnLabel); @@ -359,15 +354,18 @@ public void ProcessStatementSpawn(DMASTProcStatementSpawn statementSpawn) { proc.AddLabel(afterSpawnLabel); } + /// + /// Global/static var declarations are handled by + /// public void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration varDeclaration) { - if (varDeclaration.IsGlobal) { return; } //Currently handled by DMObjectBuilder + if (varDeclaration.IsGlobal) { return; } DMExpression value; if (varDeclaration.Value != null) { - value = DMExpression.Create(dmObject, proc, varDeclaration.Value, varDeclaration.Type); + value = _exprBuilder.Create(varDeclaration.Value, varDeclaration.Type); - if (!varDeclaration.ValType.MatchesType(value.ValType)) { - DMCompiler.Emit(WarningCode.InvalidVarType, varDeclaration.Location, + if (!varDeclaration.ValType.MatchesType(compiler, value.ValType)) { + compiler.Emit(WarningCode.InvalidVarType, varDeclaration.Location, $"{varDeclaration.Name}: Invalid var value {value.ValType}, expected {varDeclaration.ValType}"); } } else { @@ -376,8 +374,8 @@ public void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration varD bool successful; if (varDeclaration.IsConst) { - if (!value.TryAsConstant(out var constValue)) { - DMCompiler.Emit(WarningCode.HardConstContext, varDeclaration.Location, "Const var must be set to a constant"); + if (!value.TryAsConstant(compiler, out var constValue)) { + compiler.Emit(WarningCode.HardConstContext, varDeclaration.Location, "Const var must be set to a constant"); return; } @@ -387,29 +385,29 @@ public void ProcessStatementVarDeclaration(DMASTProcStatementVarDeclaration varD } if (!successful) { - DMCompiler.Emit(WarningCode.DuplicateVariable, varDeclaration.Location, $"Duplicate var {varDeclaration.Name}"); + compiler.Emit(WarningCode.DuplicateVariable, varDeclaration.Location, $"Duplicate var {varDeclaration.Name}"); return; } - value.EmitPushValue(dmObject, proc); + value.EmitPushValue(ExprContext); proc.Assign(proc.GetLocalVariableReference(varDeclaration.Name)); proc.Pop(); } public void ProcessStatementReturn(DMASTProcStatementReturn statement) { if (statement.Value != null) { - var expr = DMExpression.Create(dmObject, proc, statement.Value); + var expr = _exprBuilder.Create(statement.Value); // Don't type-check unimplemented procs if (proc.TypeChecked && (proc.Attributes & ProcAttributes.Unimplemented) == 0) { - if (expr.TryAsConstant(out var exprConst)) { + if (expr.TryAsConstant(compiler, out var exprConst)) { proc.ValidateReturnType(exprConst); } else { proc.ValidateReturnType(expr); } } - expr.EmitPushValue(dmObject, proc); + expr.EmitPushValue(ExprContext); } else { proc.PushReferenceValue(DMReference.Self); //Default return value } @@ -418,7 +416,7 @@ public void ProcessStatementReturn(DMASTProcStatementReturn statement) { } public void ProcessStatementIf(DMASTProcStatementIf statement) { - DMExpression.Emit(dmObject, proc, statement.Condition); + _exprBuilder.Emit(statement.Condition); if (statement.ElseBody == null) { string endLabel = proc.NewLabelName(); @@ -455,22 +453,22 @@ public void ProcessStatementFor(DMASTProcStatementFor statementFor) { } if (statementFor.Expression2 != null || statementFor.Expression3 != null) { - var initializer = statementFor.Expression1 != null ? DMExpression.Create(dmObject, proc, statementFor.Expression1) : null; - var comparator = statementFor.Expression2 != null ? DMExpression.Create(dmObject, proc, statementFor.Expression2) : null; - var incrementor = statementFor.Expression3 != null ? DMExpression.Create(dmObject, proc, statementFor.Expression3) : null; + var initializer = statementFor.Expression1 != null ? _exprBuilder.Create(statementFor.Expression1) : null; + var comparator = statementFor.Expression2 != null ? _exprBuilder.Create(statementFor.Expression2) : null; + var incrementor = statementFor.Expression3 != null ? _exprBuilder.Create(statementFor.Expression3) : null; ProcessStatementForStandard(initializer, comparator, incrementor, statementFor.Body); } else { switch (statementFor.Expression1) { case DMASTAssign {LHS: DMASTVarDeclExpression decl, RHS: DMASTExpressionInRange range}: { - var initializer = statementFor.Expression1 != null ? DMExpression.Create(dmObject, proc, statementFor.Expression1) : null; + var initializer = statementFor.Expression1 != null ? _exprBuilder.Create(statementFor.Expression1) : null; var identifier = new DMASTIdentifier(decl.Location, decl.DeclPath.Path.LastElement); - var outputVar = DMExpression.Create(dmObject, proc, identifier); + var outputVar = _exprBuilder.Create(identifier); - var start = DMExpression.Create(dmObject, proc, range.StartRange); - var end = DMExpression.Create(dmObject, proc, range.EndRange); + var start = _exprBuilder.Create(range.StartRange); + var end = _exprBuilder.Create(range.EndRange); var step = range.Step != null - ? DMExpression.Create(dmObject, proc, range.Step) + ? _exprBuilder.Create(range.StartRange) : new Number(range.Location, 1); ProcessStatementForRange(initializer, outputVar, start, end, step, statementFor.Body); @@ -489,22 +487,22 @@ public void ProcessStatementFor(DMASTProcStatementFor statementFor) { outputExpr = exprRange.Value; } - var outputVar = DMExpression.Create(dmObject, proc, outputExpr); + var outputVar = _exprBuilder.Create(outputExpr); - var start = DMExpression.Create(dmObject, proc, exprRange.StartRange); - var end = DMExpression.Create(dmObject, proc, exprRange.EndRange); + var start = _exprBuilder.Create(exprRange.StartRange); + var end = _exprBuilder.Create(exprRange.EndRange); var step = exprRange.Step != null - ? DMExpression.Create(dmObject, proc, exprRange.Step) + ? _exprBuilder.Create(exprRange.Step) : new Number(exprRange.Location, 1); ProcessStatementForRange(null, outputVar, start, end, step, statementFor.Body); break; } case DMASTVarDeclExpression vd: { - var initializer = statementFor.Expression1 != null ? DMExpression.Create(dmObject, proc, statementFor.Expression1) : null; + var initializer = statementFor.Expression1 != null ? _exprBuilder.Create(statementFor.Expression1) : null; var declInfo = new ProcVarDeclInfo(vd.DeclPath.Path); var identifier = new DMASTIdentifier(vd.Location, declInfo.VarName); - var outputVar = DMExpression.Create(dmObject, proc, identifier); + var outputVar = _exprBuilder.Create(identifier); ProcessStatementForType(initializer, outputVar, declInfo.TypePath, statementFor.Body); break; @@ -517,8 +515,8 @@ public void ProcessStatementFor(DMASTProcStatementFor statementFor) { outputExpr = exprIn.LHS; } - var outputVar = DMExpression.Create(dmObject, proc, outputExpr); - var list = DMExpression.Create(dmObject, proc, exprIn.RHS); + var outputVar = _exprBuilder.Create(outputExpr); + var list = _exprBuilder.Create(exprIn.RHS); if (outputVar is Local outputLocal) { outputLocal.LocalVar.ExplicitValueType = statementFor.DMTypes; @@ -528,7 +526,7 @@ public void ProcessStatementFor(DMASTProcStatementFor statementFor) { break; } default: - DMCompiler.Emit(WarningCode.BadExpression, statementFor.Location, "Invalid expression in for"); + compiler.Emit(WarningCode.BadExpression, statementFor.Location, "Invalid expression in for"); break; } } @@ -551,7 +549,7 @@ public void ProcessStatementForStandard(DMExpression? initializer, DMExpression? proc.StartScope(); { if (initializer != null) { - initializer.EmitPushValue(dmObject, proc); + initializer.EmitPushValue(ExprContext); proc.Pop(); } @@ -559,7 +557,7 @@ public void ProcessStatementForStandard(DMExpression? initializer, DMExpression? proc.LoopStart(loopLabel); { if (comparator != null) { - comparator.EmitPushValue(dmObject, proc); + comparator.EmitPushValue(ExprContext); proc.BreakIfFalse(); } @@ -567,9 +565,10 @@ public void ProcessStatementForStandard(DMExpression? initializer, DMExpression? proc.MarkLoopContinue(loopLabel); if (incrementor != null) { - incrementor.EmitPushValue(dmObject, proc); + incrementor.EmitPushValue(ExprContext); proc.Pop(); } + proc.LoopJumpToStart(loopLabel); } proc.LoopEnd(); @@ -582,7 +581,7 @@ public void ProcessLoopAssignment(LValue lValue) { string endLabel = proc.NewLabelName(); string endLabel2 = proc.NewLabelName(); - DMReference outputRef = lValue.EmitReference(dmObject, proc, endLabel, DMExpression.ShortCircuitMode.PopNull); + DMReference outputRef = lValue.EmitReference(ExprContext, endLabel, DMExpression.ShortCircuitMode.PopNull); proc.Enumerate(outputRef); proc.Jump(endLabel2); @@ -590,14 +589,14 @@ public void ProcessLoopAssignment(LValue lValue) { proc.EnumerateNoAssign(); proc.AddLabel(endLabel2); } else { - DMReference outputRef = lValue.EmitReference(dmObject, proc, null); + DMReference outputRef = lValue.EmitReference(ExprContext, null); proc.Enumerate(outputRef); } } public void ProcessStatementForList(DMExpression list, DMExpression outputVar, DMComplexValueType? dmTypes, DMASTProcBlockInner body) { if (outputVar is not LValue lValue) { - DMCompiler.Emit(WarningCode.BadExpression, outputVar.Location, "Invalid output var"); + compiler.Emit(WarningCode.BadExpression, outputVar.Location, "Invalid output var"); lValue = null; } @@ -611,17 +610,17 @@ public void ProcessStatementForList(DMExpression list, DMExpression outputVar, D implicitTypeCheck = dmTypes.Value.TypePath; } else if (!dmTypes.Value.IsAnything) { // "as anything" performs no check. Other values are unimplemented. - DMCompiler.UnimplementedWarning(outputVar.Location, + compiler.UnimplementedWarning(outputVar.Location, $"As type {dmTypes} in for loops is unimplemented. No type check will be performed."); } - list.EmitPushValue(dmObject, proc); + list.EmitPushValue(ExprContext); if (implicitTypeCheck != null) { - if (DMObjectTree.TryGetTypeId(implicitTypeCheck.Value, out var filterTypeId)) { + if (compiler.DMObjectTree.TryGetTypeId(implicitTypeCheck.Value, out var filterTypeId)) { // Create an enumerator that will do the implicit istype() for us proc.CreateFilteredListEnumerator(filterTypeId, implicitTypeCheck.Value); } else { - DMCompiler.Emit(WarningCode.ItemDoesntExist, outputVar.Location, + compiler.Emit(WarningCode.ItemDoesntExist, outputVar.Location, $"Cannot filter enumeration by type {implicitTypeCheck.Value}, it does not exist"); proc.CreateListEnumerator(); } @@ -652,22 +651,22 @@ public void ProcessStatementForList(DMExpression list, DMExpression outputVar, D public void ProcessStatementForType(DMExpression? initializer, DMExpression outputVar, DreamPath? type, DMASTProcBlockInner body) { if (type == null) { // This shouldn't happen, just to be safe - DMCompiler.ForcedError(initializer.Location, + compiler.ForcedError(initializer.Location, "Attempted to create a type enumerator with a null type"); return; } - if (DMObjectTree.TryGetTypeId(type.Value, out var typeId)) { + if (compiler.DMObjectTree.TryGetTypeId(type.Value, out var typeId)) { proc.PushType(typeId); proc.CreateTypeEnumerator(); } else { - DMCompiler.Emit(WarningCode.ItemDoesntExist, initializer.Location, $"Type {type.Value} does not exist"); + compiler.Emit(WarningCode.ItemDoesntExist, initializer.Location, $"Type {type.Value} does not exist"); } proc.StartScope(); { if (initializer != null) { - initializer.EmitPushValue(dmObject, proc); + initializer.EmitPushValue(ExprContext); proc.Pop(); } @@ -679,7 +678,7 @@ public void ProcessStatementForType(DMExpression? initializer, DMExpression outp if (outputVar is Expressions.LValue lValue) { ProcessLoopAssignment(lValue); } else { - DMCompiler.Emit(WarningCode.BadExpression, outputVar.Location, "Invalid output var"); + compiler.Emit(WarningCode.BadExpression, outputVar.Location, "Invalid output var"); } ProcessBlockInner(body); @@ -692,10 +691,10 @@ public void ProcessStatementForType(DMExpression? initializer, DMExpression outp } public void ProcessStatementForRange(DMExpression? initializer, DMExpression outputVar, DMExpression start, DMExpression end, DMExpression? step, DMASTProcBlockInner body) { - start.EmitPushValue(dmObject, proc); - end.EmitPushValue(dmObject, proc); + start.EmitPushValue(ExprContext); + end.EmitPushValue(ExprContext); if (step != null) { - step.EmitPushValue(dmObject, proc); + step.EmitPushValue(ExprContext); } else { proc.PushFloat(1.0f); } @@ -704,7 +703,7 @@ public void ProcessStatementForRange(DMExpression? initializer, DMExpression out proc.StartScope(); { if (initializer != null) { - initializer.EmitPushValue(dmObject, proc); + initializer.EmitPushValue(ExprContext); proc.Pop(); } @@ -716,7 +715,7 @@ public void ProcessStatementForRange(DMExpression? initializer, DMExpression out if (outputVar is Expressions.LValue lValue) { ProcessLoopAssignment(lValue); } else { - DMCompiler.Emit(WarningCode.BadExpression, outputVar.Location, "Invalid output var"); + compiler.Emit(WarningCode.BadExpression, outputVar.Location, "Invalid output var"); } ProcessBlockInner(body); @@ -750,7 +749,7 @@ public void ProcessStatementWhile(DMASTProcStatementWhile statementWhile) { proc.LoopStart(loopLabel); { proc.MarkLoopContinue(loopLabel); - DMExpression.Emit(dmObject, proc, statementWhile.Conditional); + _exprBuilder.Emit(statementWhile.Conditional); proc.BreakIfFalse(); proc.StartScope(); @@ -772,7 +771,7 @@ public void ProcessStatementDoWhile(DMASTProcStatementDoWhile statementDoWhile) ProcessBlockInner(statementDoWhile.Body); proc.MarkLoopContinue(loopLabel); - DMExpression.Emit(dmObject, proc, statementDoWhile.Conditional); + _exprBuilder.Emit(statementDoWhile.Conditional); proc.JumpIfFalse(loopEndLabel); proc.LoopJumpToStart(loopLabel); @@ -787,15 +786,15 @@ public void ProcessStatementSwitch(DMASTProcStatementSwitch statementSwitch) { List<(string CaseLabel, DMASTProcBlockInner CaseBody)> valueCases = new(); DMASTProcBlockInner? defaultCaseBody = null; - DMExpression.Emit(dmObject, proc, statementSwitch.Value); + _exprBuilder.Emit(statementSwitch.Value); foreach (DMASTProcStatementSwitch.SwitchCase switchCase in statementSwitch.Cases) { if (switchCase is DMASTProcStatementSwitch.SwitchCaseValues switchCaseValues) { string caseLabel = proc.NewLabelName(); foreach (DMASTExpression value in switchCaseValues.Values) { Constant GetCaseValue(DMASTExpression expression) { - if (!DMExpression.TryConstant(dmObject, proc, expression, out var constant)) - DMCompiler.Emit(WarningCode.HardConstContext, expression.Location, "Expected a constant"); + if (!_exprBuilder.TryConstant(expression, out var constant)) + compiler.Emit(WarningCode.HardConstContext, expression.Location, "Expected a constant"); // Return 0 if unsuccessful so that we can continue compiling return constant ?? new Number(expression.Location, 0.0f); @@ -807,7 +806,7 @@ Constant GetCaseValue(DMASTExpression expression) { Constant CoerceBound(Constant bound) { if (bound is Null) { // We do a little null coercion, as a treat - DMCompiler.Emit(WarningCode.MalformedRange, range.RangeStart.Location, + compiler.Emit(WarningCode.MalformedRange, range.RangeStart.Location, "Malformed range, lower bound is coerced from null to 0"); return new Number(lower.Location, 0.0f); } @@ -815,7 +814,7 @@ Constant CoerceBound(Constant bound) { //DM 514.1580 does NOT care if the constants within a range are strings, and does a strange conversion to 0 or something, without warning or notice. //We are (hopefully) deviating from parity here and just calling that a Compiler error. if (bound is not Number) { - DMCompiler.Emit(WarningCode.InvalidRange, range.RangeStart.Location, + compiler.Emit(WarningCode.InvalidRange, range.RangeStart.Location, "Invalid range, lower bound is not a number"); bound = new Number(bound.Location, 0.0f); } @@ -826,13 +825,13 @@ Constant CoerceBound(Constant bound) { lower = CoerceBound(lower); upper = CoerceBound(upper); - lower.EmitPushValue(dmObject, proc); - upper.EmitPushValue(dmObject, proc); + lower.EmitPushValue(ExprContext); + upper.EmitPushValue(ExprContext); proc.SwitchCaseRange(caseLabel); } else { Constant constant = GetCaseValue(value); - constant.EmitPushValue(dmObject, proc); + constant.EmitPushValue(ExprContext); proc.SwitchCase(caseLabel); } } @@ -867,74 +866,74 @@ Constant CoerceBound(Constant bound) { } public void ProcessStatementBrowse(DMASTProcStatementBrowse statementBrowse) { - DMExpression.Emit(dmObject, proc, statementBrowse.Receiver); - DMExpression.Emit(dmObject, proc, statementBrowse.Body); - DMExpression.Emit(dmObject, proc, statementBrowse.Options); + _exprBuilder.Emit(statementBrowse.Receiver); + _exprBuilder.Emit(statementBrowse.Body); + _exprBuilder.Emit(statementBrowse.Options); proc.Browse(); } public void ProcessStatementBrowseResource(DMASTProcStatementBrowseResource statementBrowseResource) { - DMExpression.Emit(dmObject, proc, statementBrowseResource.Receiver); - DMExpression.Emit(dmObject, proc, statementBrowseResource.File); - DMExpression.Emit(dmObject, proc, statementBrowseResource.Filename); + _exprBuilder.Emit(statementBrowseResource.Receiver); + _exprBuilder.Emit(statementBrowseResource.File); + _exprBuilder.Emit(statementBrowseResource.Filename); proc.BrowseResource(); } public void ProcessStatementOutputControl(DMASTProcStatementOutputControl statementOutputControl) { - DMExpression.Emit(dmObject, proc, statementOutputControl.Receiver); - DMExpression.Emit(dmObject, proc, statementOutputControl.Message); - DMExpression.Emit(dmObject, proc, statementOutputControl.Control); + _exprBuilder.Emit(statementOutputControl.Receiver); + _exprBuilder.Emit(statementOutputControl.Message); + _exprBuilder.Emit(statementOutputControl.Control); proc.OutputControl(); } public void ProcessStatementFtp(DMASTProcStatementFtp statementFtp) { - DMExpression.Emit(dmObject, proc, statementFtp.Receiver); - DMExpression.Emit(dmObject, proc, statementFtp.File); - DMExpression.Emit(dmObject, proc, statementFtp.Name); + _exprBuilder.Emit(statementFtp.Receiver); + _exprBuilder.Emit(statementFtp.File); + _exprBuilder.Emit(statementFtp.Name); proc.Ftp(); } public void ProcessStatementOutput(DMASTProcStatementOutput statementOutput) { - DMExpression left = DMExpression.Create(dmObject, proc, statementOutput.A); - DMExpression right = DMExpression.Create(dmObject, proc, statementOutput.B); + DMExpression left = _exprBuilder.Create(statementOutput.A); + DMExpression right = _exprBuilder.Create(statementOutput.B); if (left is LValue) { // An LValue on the left needs a special opcode so that its reference can be used // This allows for special operations like "savefile[...] << ..." string endLabel = proc.NewLabelName(); - DMReference leftRef = left.EmitReference(dmObject, proc, endLabel, DMExpression.ShortCircuitMode.PopNull); - right.EmitPushValue(dmObject, proc); + DMReference leftRef = left.EmitReference(ExprContext, endLabel, DMExpression.ShortCircuitMode.PopNull); + right.EmitPushValue(ExprContext); proc.OutputReference(leftRef); proc.AddLabel(endLabel); } else { - left.EmitPushValue(dmObject, proc); - right.EmitPushValue(dmObject, proc); + left.EmitPushValue(ExprContext); + right.EmitPushValue(ExprContext); proc.Output(); } } public void ProcessStatementInput(DMASTProcStatementInput statementInput) { - DMExpression left = DMExpression.Create(dmObject, proc, statementInput.A); - DMExpression right = DMExpression.Create(dmObject, proc, statementInput.B); + DMExpression left = _exprBuilder.Create(statementInput.A); + DMExpression right = _exprBuilder.Create(statementInput.B); // The left-side value of an input operation must be an LValue // (I think? I haven't found an exception but there could be one) if (left is not LValue) { - DMCompiler.Emit(WarningCode.BadExpression, left.Location, "Left side must be an l-value"); + compiler.Emit(WarningCode.BadExpression, left.Location, "Left side must be an l-value"); return; } // The right side must also be an LValue. Because where else would the value go? if (right is not LValue) { - DMCompiler.Emit(WarningCode.BadExpression, right.Location, "Right side must be an l-value"); + compiler.Emit(WarningCode.BadExpression, right.Location, "Right side must be an l-value"); return; } string rightEndLabel = proc.NewLabelName(); string leftEndLabel = proc.NewLabelName(); - DMReference rightRef = right.EmitReference(dmObject, proc, rightEndLabel, DMExpression.ShortCircuitMode.PopNull); - DMReference leftRef = left.EmitReference(dmObject, proc, leftEndLabel, DMExpression.ShortCircuitMode.PopNull); + DMReference rightRef = right.EmitReference(ExprContext, rightEndLabel, DMExpression.ShortCircuitMode.PopNull); + DMReference leftRef = left.EmitReference(ExprContext, leftEndLabel, DMExpression.ShortCircuitMode.PopNull); proc.Input(leftRef, rightRef); @@ -950,7 +949,7 @@ public void ProcessStatementTryCatch(DMASTProcStatementTryCatch tryCatch) { var param = tryCatch.CatchParameter as DMASTProcStatementVarDeclaration; if (!proc.TryAddLocalVariable(param.Name, param.Type, param.ValType)) { - DMCompiler.Emit(WarningCode.DuplicateVariable, param.Location, $"Duplicate var {param.Name}"); + compiler.Emit(WarningCode.DuplicateVariable, param.Location, $"Duplicate var {param.Name}"); } proc.StartTry(catchLabel, proc.GetLocalVariableReference(param.Name)); @@ -970,12 +969,12 @@ public void ProcessStatementTryCatch(DMASTProcStatementTryCatch tryCatch) { ProcessBlockInner(tryCatch.CatchBody); proc.EndScope(); } - proc.AddLabel(endLabel); + proc.AddLabel(endLabel); } public void ProcessStatementThrow(DMASTProcStatementThrow statement) { - DMExpression.Emit(dmObject, proc, statement.Value); + _exprBuilder.Emit(statement.Value); proc.Throw(); } } diff --git a/DMCompiler/DM/DMCodeTree.Procs.cs b/DMCompiler/DM/DMCodeTree.Procs.cs new file mode 100644 index 0000000000..174ff77c9b --- /dev/null +++ b/DMCompiler/DM/DMCodeTree.Procs.cs @@ -0,0 +1,119 @@ +using DMCompiler.Compiler; +using DMCompiler.Compiler.DM.AST; + +namespace DMCompiler.DM; + +internal partial class DMCodeTree { + private class ProcsNode() : TypeNode("proc"); + + private class ProcNode(DMCodeTree codeTree, DreamPath owner, DMASTProcDefinition procDef) : TypeNode(procDef.Name) { + private string ProcName => procDef.Name; + private bool IsOverride => procDef.IsOverride; + + private bool _defined; + + public bool TryDefineProc(DMCompiler compiler) { + if (_defined) + return true; + if (!compiler.DMObjectTree.TryGetDMObject(owner, out var dmObject)) + return false; + + _defined = true; + + bool hasProc = dmObject.HasProc(ProcName); + if (hasProc && !IsOverride && !dmObject.OwnsProc(ProcName) && !procDef.Location.InDMStandard) { + compiler.Emit(WarningCode.DuplicateProcDefinition, procDef.Location, + $"Type {owner} already inherits a proc named \"{ProcName}\" and cannot redefine it"); + return true; // TODO: Maybe fallthrough since this error is a little pedantic? + } + + DMProc proc = compiler.DMObjectTree.CreateDMProc(dmObject, procDef); + + if (dmObject == compiler.DMObjectTree.Root) { // Doesn't belong to a type, this is a global proc + if(IsOverride) { + compiler.Emit(WarningCode.InvalidOverride, procDef.Location, + $"Global procs cannot be overridden - '{ProcName}' override will be ignored"); + //Continue processing the proc anyhoo, just don't add it. + } else { + compiler.VerbosePrint($"Adding global proc {procDef.Name}() on pass {codeTree._currentPass}"); + compiler.DMObjectTree.AddGlobalProc(proc); + } + } else { + compiler.VerbosePrint($"Adding proc {procDef.Name}() to {dmObject.Path} on pass {codeTree._currentPass}"); + dmObject.AddProc(proc, forceFirst: procDef.Location.InDMStandard); + } + + foreach (var varDecl in GetVarDeclarations()) { + if (!varDecl.IsGlobal) + continue; + + var procGlobalNode = new ProcGlobalVarNode(owner, proc, varDecl); + Children.Add(procGlobalNode); + codeTree._waitingNodes.Add(procGlobalNode); + } + + if (proc.IsVerb) { + dmObject.AddVerb(proc); + } + + return true; + } + + // TODO: Remove this entirely + private IEnumerable GetVarDeclarations() { + var statements = new Queue(procDef.Body?.Statements ?? []); + + static void AddBody(Queue queue, DMASTProcBlockInner? block) { + if (block is null) + return; + + foreach (var stmt in block.Statements) + queue.Enqueue(stmt); + } + + while (statements.TryDequeue(out var stmt)) { + switch (stmt) { + // TODO multiple var definitions. + case DMASTProcStatementVarDeclaration ps: yield return ps; break; + + case DMASTProcStatementSpawn ps: AddBody(statements, ps.Body); break; + case DMASTProcStatementFor ps: AddBody(statements, ps.Body); break; + case DMASTProcStatementWhile ps: AddBody(statements, ps.Body); break; + case DMASTProcStatementDoWhile ps: AddBody(statements, ps.Body); break; + case DMASTProcStatementInfLoop ps: AddBody(statements, ps.Body); break; + case DMASTProcStatementIf ps: + AddBody(statements, ps.Body); + AddBody(statements, ps.ElseBody); + break; + case DMASTProcStatementTryCatch ps: + AddBody(statements, ps.TryBody); + AddBody(statements, ps.CatchBody); + break; + // TODO Good luck if you declare a static var inside a switch + case DMASTProcStatementSwitch ps: { + foreach (var swCase in ps.Cases) { + AddBody(statements, swCase.Body); + } + + break; + } + } + } + } + + public override string ToString() { + return ProcName + "()"; + } + } + + public void AddProc(DreamPath owner, DMASTProcDefinition procDef) { + var node = GetDMObjectNode(owner); + var procNode = new ProcNode(this, owner, procDef); + + if (procDef is { Name: "New", IsOverride: false }) + _newProcs[owner] = procNode; // We need to be ready to define New() as soon as the type is created + + node.AddProcsNode().Children.Add(procNode); + _waitingNodes.Add(procNode); + } +} diff --git a/DMCompiler/DM/DMCodeTree.Vars.cs b/DMCompiler/DM/DMCodeTree.Vars.cs new file mode 100644 index 0000000000..0657c8cccd --- /dev/null +++ b/DMCompiler/DM/DMCodeTree.Vars.cs @@ -0,0 +1,316 @@ +using System.Diagnostics.CodeAnalysis; +using DMCompiler.Bytecode; +using DMCompiler.Compiler; +using DMCompiler.Compiler.DM.AST; +using DMCompiler.DM.Builders; +using DMCompiler.DM.Expressions; +using ScopeMode = DMCompiler.DM.Builders.DMExpressionBuilder.ScopeMode; + +namespace DMCompiler.DM; + +internal partial class DMCodeTree { + public abstract class VarNode : INode { + public UnknownReference? LastError; + + protected bool IsFirstPass => (LastError == null); + + public abstract bool TryDefineVar(DMCompiler compiler, int pass); + + protected bool TryBuildValue(ExpressionContext ctx, DMASTExpression ast, DreamPath? inferredType, + ScopeMode scope, [NotNullWhen(true)] out DMExpression? value) { + var exprBuilder = new DMExpressionBuilder(ctx, scope); + + value = exprBuilder.CreateIgnoreUnknownReference(ast, inferredType); + if (value is UnknownReference unknownRef) { + LastError = unknownRef; + value = null; + return false; + } + + return true; + } + + protected void SetVariableValue(DMCompiler compiler, DMObject dmObject, DMVariable variable, DMExpression value, bool isOverride) { + // Typechecking + if (!variable.ValType.MatchesType(compiler, value.ValType) && !variable.ValType.IsUnimplemented && !variable.ValType.Type.HasFlag(DMValueType.NoConstFold)) { + if (value is Null && !isOverride) { + compiler.Emit(WarningCode.ImplicitNullType, value.Location, $"{dmObject.Path}.{variable.Name}: Variable is null but not explicitly typed as nullable, append \"|null\" to \"as\". Implicitly treating as nullable."); + variable.ValType |= DMValueType.Null; + } else { + compiler.Emit(WarningCode.InvalidVarType, value.Location, $"{dmObject.Path}.{variable.Name}: Invalid var value type {value.ValType}, expected {variable.ValType}"); + } + } + + if (value.TryAsConstant(compiler, out var constant)) { + variable.Value = constant; + return; + } else if (variable.IsConst) { + compiler.Emit(WarningCode.HardConstContext, value.Location, "Value of const var must be a constant"); + return; + } + + if (!IsValidRightHandSide(compiler, dmObject, value)) { + compiler.Emit(WarningCode.BadExpression, value.Location, + $"Invalid initial value for \"{variable.Name}\""); + return; + } + + var initLoc = value.Location; + var field = new Field(initLoc, variable, variable.ValType); + var assign = new Assignment(initLoc, field, value); + + variable.Value = new Null(Location.Internal); + dmObject.InitializationProcExpressions.Add(assign); + } + + /// Whether the given value can be used as an instance variable's initial value + private bool IsValidRightHandSide(DMCompiler compiler, DMObject dmObject, DMExpression value) { + return value switch { + //TODO: A better way of handling procs evaluated at compile time + ProcCall procCall => procCall.GetTargetProc(compiler, dmObject).Proc?.Name switch { + "generator" => true, + "matrix" => true, + "icon" => true, + "file" => true, + "sound" => true, + "nameof" => true, + _ => false + }, + + List => true, + DimensionalList => true, + NewList => true, + NewPath => true, + Rgb => true, + // TODO: Check for circular reference loops here + // (Note that we do accidentally support global-field access somewhat when it gets const-folded by TryAsConstant before we get here) + GlobalField => false, + _ => false + }; + } + } + + private class ObjectVarNode(DreamPath owner, DMASTObjectVarDefinition varDef) : VarNode { + private string VarName => varDef.Name; + private bool IsStatic => varDef.IsStatic; + + private bool _defined; + + public override bool TryDefineVar(DMCompiler compiler, int pass) { + if (_defined) + return true; + if (!compiler.DMObjectTree.TryGetDMObject(owner, out var dmObject)) + return false; + + if (AlreadyExists(compiler, dmObject)) { + _defined = true; + return true; + } + + if (IsStatic) { + return HandleGlobalVar(compiler, dmObject, pass); + } else { + return HandleInstanceVar(compiler, dmObject); + } + } + + public override string ToString() { + return varDef.IsStatic ? $"var/static/{VarName}" : $"var/{VarName}"; + } + + private bool HandleGlobalVar(DMCompiler compiler, DMObject dmObject, int pass) { + var scope = IsFirstPass ? ScopeMode.FirstPassStatic : ScopeMode.Static; + if (!TryBuildValue(new(compiler, dmObject, compiler.GlobalInitProc), varDef.Value, varDef.Type, scope, out var value)) + return false; + + int globalId = compiler.DMObjectTree.CreateGlobal(out DMVariable global, varDef.Type, VarName, varDef.IsConst, + varDef.ValType); + + dmObject.AddGlobalVariable(global, globalId); + _defined = true; + + if (value.TryAsConstant(compiler, out var constant)) { + global.Value = constant; + return true; + } else if (!global.IsConst) { + // Starts out as null, gets initialized by the global init proc + global.Value = new Null(Location.Internal); + } else { + compiler.Emit(WarningCode.HardConstContext, value.Location, "Constant initializer required"); + } + + // Initialize its value in the global init proc + compiler.VerbosePrint($"Adding {dmObject.Path}/var/static/{global.Name} to global init on pass {pass}"); + compiler.GlobalInitProc.DebugSource(value.Location); + value.EmitPushValue(new(compiler, dmObject, compiler.GlobalInitProc)); + compiler.GlobalInitProc.Assign(DMReference.CreateGlobal(globalId)); + return true; + } + + private bool HandleInstanceVar(DMCompiler compiler, DMObject dmObject) { + if (!TryBuildValue(new(compiler, dmObject, null), varDef.Value, varDef.Type, ScopeMode.Normal, out var value)) + return false; + + var variable = new DMVariable(varDef.Type, VarName, false, varDef.IsConst, varDef.IsTmp, varDef.ValType); + dmObject.AddVariable(variable); + _defined = true; + + SetVariableValue(compiler, dmObject, variable, value, false); + return true; + } + + private bool AlreadyExists(DMCompiler compiler, DMObject dmObject) { + // "type" and "tag" can only be defined in DMStandard + if (VarName is "type" or "tag" && !varDef.Location.InDMStandard) { + compiler.Emit(WarningCode.InvalidVarDefinition, varDef.Location, + $"Cannot redefine built-in var \"{VarName}\""); + return true; + } + + //DMObjects store two bundles of variables; the statics in GlobalVariables and the non-statics in Variables. + if (dmObject.HasGlobalVariable(VarName)) { + compiler.Emit(WarningCode.InvalidVarDefinition, varDef.Location, + $"Duplicate definition of static var \"{VarName}\""); + return true; + } else if (dmObject.HasLocalVariable(VarName)) { + if (!varDef.Location.InDMStandard) // Duplicate instance vars are not an error in DMStandard + compiler.Emit(WarningCode.InvalidVarDefinition, varDef.Location, + $"Duplicate definition of var \"{VarName}\""); + return true; + } else if (IsStatic && VarName == "vars" && dmObject == compiler.DMObjectTree.Root) { + compiler.Emit(WarningCode.InvalidVarDefinition, varDef.Location, "Duplicate definition of global.vars"); + return true; + } + + return false; + } + } + + private class ObjectVarOverrideNode(DreamPath owner, DMASTObjectVarOverride varOverride) : VarNode { + private string VarName => varOverride.VarName; + + private bool _finished; + + public override bool TryDefineVar(DMCompiler compiler, int pass) { + if (_finished) + return true; + if (!compiler.DMObjectTree.TryGetDMObject(owner, out var dmObject)) + return false; + + DMVariable? variable = null; + if (dmObject.HasLocalVariable(VarName)) { + variable = dmObject.GetVariable(VarName); + } else if (dmObject.HasGlobalVariable(VarName)) { + compiler.Emit(WarningCode.StaticOverride, varOverride.Location, + $"var \"{VarName}\" cannot be overridden - it is a global var"); + _finished = true; + return true; + } + + if (variable == null) { + return false; + } else if (variable.IsConst) { + compiler.Emit(WarningCode.WriteToConstant, varOverride.Location, + $"Var \"{VarName}\" is const and cannot be modified"); + _finished = true; + return true; + } else if (variable.ValType.IsCompileTimeReadOnly) { + compiler.Emit(WarningCode.WriteToConstant, varOverride.Location, + $"Var \"{VarName}\" is a native read-only value which cannot be modified"); + _finished = true; + return true; + } + + variable = new DMVariable(variable); + + if (!TryBuildValue(new(compiler, dmObject, null), varOverride.Value, variable.Type, ScopeMode.Normal, out var value)) + return false; + + if (VarName == "tag" && dmObject.IsSubtypeOf(DreamPath.Datum) && !compiler.Settings.NoStandard) + compiler.Emit(WarningCode.InvalidOverride, varOverride.Location, + "var \"tag\" cannot be set to a value at compile-time"); + + dmObject.VariableOverrides[variable.Name] = variable; + _finished = true; + + SetVariableValue(compiler, dmObject, variable, value, true); + return true; + } + + public override string ToString() { + return $"{varOverride.VarName} {{override}}"; + } + } + + private class ProcGlobalVarNode(DreamPath owner, DMProc proc, DMASTProcStatementVarDeclaration varDecl) : VarNode { + private bool _defined; + + public override bool TryDefineVar(DMCompiler compiler, int pass) { + if (_defined) + return true; + if (!compiler.DMObjectTree.TryGetDMObject(owner, out var dmObject)) + return false; + + DMExpression? value = null; + if (varDecl.Value != null) { + var scope = IsFirstPass ? ScopeMode.FirstPassStatic : ScopeMode.Static; + if (!TryBuildValue(new(compiler, dmObject, proc), varDecl.Value, varDecl.Type, scope, out value)) + return false; + } + + int globalId = compiler.DMObjectTree.CreateGlobal(out DMVariable global, varDecl.Type, varDecl.Name, varDecl.IsConst, + varDecl.ValType); + + global.Value = new Null(Location.Internal); + proc.AddGlobalVariable(global, globalId); + _defined = true; + + if (value != null) { + // Initialize its value in the global init proc + compiler.VerbosePrint($"Adding {dmObject.Path}/proc/{proc.Name}/var/static/{global.Name} to global init on pass {pass}"); + compiler.GlobalInitProc.DebugSource(value.Location); + value.EmitPushValue(new(compiler, dmObject, compiler.GlobalInitProc)); + compiler.GlobalInitProc.Assign(DMReference.CreateGlobal(globalId)); + } + + return true; + } + + public override string ToString() { + return $"var/static/{varDecl.Name}"; + } + } + + public void AddObjectVar(DreamPath owner, DMASTObjectVarDefinition varDef) { + var node = GetDMObjectNode(owner); + var varNode = new ObjectVarNode(owner, varDef); + + node.Children.Add(varNode); + _waitingNodes.Add(varNode); + } + + public void AddObjectVarOverride(DreamPath owner, DMASTObjectVarOverride varOverride) { + var node = GetDMObjectNode(owner); + + // parent_type is not an actual var override, and must be applied as soon as the object is created + if (varOverride.VarName == "parent_type") { + if (_parentTypes.ContainsKey(owner)) { + _compiler.Emit(WarningCode.InvalidOverride, varOverride.Location, + $"{owner} already has its parent_type set. This override is ignored."); + return; + } + + if (varOverride.Value is not DMASTConstantPath parentType) { + _compiler.Emit(WarningCode.BadExpression, varOverride.Value.Location, "Expected a constant path"); + return; + } + + _parentTypes.Add(owner, parentType.Value.Path); + return; + } + + var varNode = new ObjectVarOverrideNode(owner, varOverride); + node.Children.Add(varNode); + _waitingNodes.Add(varNode); + } +} diff --git a/DMCompiler/DM/DMCodeTree.cs b/DMCompiler/DM/DMCodeTree.cs new file mode 100644 index 0000000000..a4a1459b8e --- /dev/null +++ b/DMCompiler/DM/DMCodeTree.cs @@ -0,0 +1,235 @@ +using System.Diagnostics.CodeAnalysis; +using DMCompiler.Compiler; +using DMCompiler.DM.Builders; + +namespace DMCompiler.DM; + +/// +/// A representation of all the types, procs, and vars defined in the tree.
+/// Important in the role of defining everything & initializing statics in the correct order. +///
+// TODO: "/var" vs "var" has a different init order (same for procs) +// TODO: Path elements like /static and /global are grouped together +internal partial class DMCodeTree { + private interface INode; + + private class TypeNode(string name) : INode { + public readonly List Children = new(); + + private readonly string _name = name; + + public bool TryGetChild(string name, [NotNullWhen(true)] out TypeNode? child) { + foreach (var ourChild in Children) { + if (ourChild is not TypeNode typeNode) + continue; + + if (typeNode._name == name) { + child = typeNode; + return true; + } + } + + child = null; + return false; + } + + public override string ToString() { + return _name; + } + } + + private class ObjectNode(DMCodeTree codeTree, string name, DreamPath type) : TypeNode(name) { + private bool _defined; + private ProcsNode? _procs; + + public bool TryDefineType(DMCompiler compiler) { + if (_defined) + return true; + + DMObject? explicitParent = null; + if (codeTree._parentTypes.TryGetValue(type, out var parentType) && + !compiler.DMObjectTree.TryGetDMObject(parentType, out explicitParent)) + return false; // Parent type isn't ready yet + + _defined = true; + + var dmObject = compiler.DMObjectTree.GetOrCreateDMObject(type); + if (explicitParent != null) { + dmObject.Parent = explicitParent; + codeTree._parentTypes.Remove(type); + } + + if (codeTree._newProcs.Remove(type, out var newProcNode)) + newProcNode.TryDefineProc(compiler); + + return true; + } + + public ProcsNode AddProcsNode() { + if (_procs is null) { + _procs = new(); + Children.Add(_procs); + } + + return _procs; + } + } + + private readonly DMCompiler _compiler; + private readonly HashSet _waitingNodes = new(); + private readonly Dictionary _parentTypes = new(); + private readonly Dictionary _newProcs = new(); + private ObjectNode _root; + private ObjectNode? _dmStandardRoot; + private int _currentPass; + + public DMCodeTree(DMCompiler compiler) { + // Yep, not _dmStandardRoot + // They get switched in FinishDMStandard() + _root = new(this, "/ (DMStandard)", DreamPath.Root); + + _compiler = compiler; + } + + public void DefineEverything() { + if (_dmStandardRoot == null) + FinishDMStandard(); + + void Pass(ObjectNode root) { + foreach (var node in TraverseNodes(root)) { + var successful = (node is ObjectNode objectNode && objectNode.TryDefineType(_compiler)) || + (node is ProcNode procNode && procNode.TryDefineProc(_compiler)) || + (node is VarNode varNode && varNode.TryDefineVar(_compiler, _currentPass)); + + if (successful) + _waitingNodes.Remove(node); + } + } + + // Pass 0 + DMExpressionBuilder.ScopeOperatorEnabled = false; + Pass(_root); + Pass(_dmStandardRoot!); + + int lastCount; + do { + _currentPass++; + lastCount = _waitingNodes.Count; + + Pass(_root); + Pass(_dmStandardRoot!); + } while (_waitingNodes.Count < lastCount && _waitingNodes.Count > 0); + + // Scope operator pass + DMExpressionBuilder.ScopeOperatorEnabled = true; + Pass(_root); + Pass(_dmStandardRoot!); + + // If there exists vars that didn't successfully compile, emit their errors + foreach (var node in _waitingNodes) { + if (node is not VarNode varNode) // TODO: If a type or proc fails? + continue; + if (varNode.LastError == null) + continue; + + _compiler.Emit(WarningCode.ItemDoesntExist, varNode.LastError.Location, + varNode.LastError.Message); + } + + _compiler.GlobalInitProc.ResolveLabels(); + } + + public void FinishDMStandard() { + _dmStandardRoot = _root; + _root = new(this, "/", DreamPath.Root); + } + + public void AddType(DreamPath type) { + GetDMObjectNode(type); // Add it to our tree + } + + public DreamPath? UpwardSearch(DMObject start, DreamPath search) { + var currentPath = start.Path; + + search.Type = DreamPath.PathType.Relative; + + while (true) { + TypeNode node = GetDMObjectNode(currentPath); + + for (int i = 0; i < search.Elements.Length; i++) { + var element = search.Elements[i]; + if (element == "verb") + element = "proc"; // TODO: Separate proc and verb on the code tree + + if (!node.TryGetChild(element, out var child)) + break; + + if (i == search.Elements.Length - 1) + return currentPath.AddToPath(search.PathString); + + node = child; + } + + if (currentPath == DreamPath.Root) + return null; + currentPath = currentPath.FromElements(0, -2); + } + } + + public void Print() { + PrintNode(_root); + if (_dmStandardRoot != null) + PrintNode(_dmStandardRoot); + } + + private void PrintNode(INode node, int level = 0) { + if (node is TypeNode typeNode) { + Console.Write(new string('\t', level)); + Console.WriteLine(typeNode); + + foreach (var child in typeNode.Children) { + PrintNode(child, level + 1); + } + } else { + Console.Write(new string('\t', level)); + Console.WriteLine(node); + } + } + + private ObjectNode GetDMObjectNode(DreamPath path) { + var node = _root; + + for (int i = 0; i < path.Elements.Length; i++) { + var element = path.Elements[i]; + if (!node.TryGetChild(element, out var childNode)) { + var creating = path.FromElements(0, i + 1); + + _compiler.VerbosePrint($"Adding {creating} to the code tree"); + childNode = new ObjectNode(this, element, creating); + node.Children.Add(childNode); + _waitingNodes.Add(childNode); + } + + if (childNode is not ObjectNode objectNode) + break; + + node = objectNode; + } + + return node; + } + + private IEnumerable TraverseNodes(TypeNode from) { + yield return from; + + foreach (var child in from.Children) { + yield return child; + + if (child is TypeNode typeNode) { + using var children = TraverseNodes(typeNode).GetEnumerator(); + while (children.MoveNext()) + yield return children.Current; + } + } + } +} diff --git a/DMCompiler/DM/DMExpression.cs b/DMCompiler/DM/DMExpression.cs index 1c11e9d558..73de3895ba 100644 --- a/DMCompiler/DM/DMExpression.cs +++ b/DMCompiler/DM/DMExpression.cs @@ -1,8 +1,7 @@ using DMCompiler.Bytecode; using System.Diagnostics.CodeAnalysis; using DMCompiler.Compiler; -using DMCompiler.Compiler.DM.AST; -using DMCompiler.DM.Builders; +using DMCompiler.DM.Expressions; namespace DMCompiler.DM; @@ -11,36 +10,21 @@ internal abstract class DMExpression(Location location) { public virtual DMComplexValueType ValType => DMValueType.Anything; - // TODO: proc and dmObject can be null, address nullability contract - public static DMExpression Create(DMObject dmObject, DMProc proc, DMASTExpression expression, DreamPath? inferredPath = null) { - return DMExpressionBuilder.BuildExpression(expression, dmObject, proc, inferredPath); - } - - public static void Emit(DMObject dmObject, DMProc proc, DMASTExpression expression, DreamPath? inferredPath = null) { - var expr = Create(dmObject, proc, expression, inferredPath); - expr.EmitPushValue(dmObject, proc); - } - - public static bool TryConstant(DMObject dmObject, DMProc proc, DMASTExpression expression, out Expressions.Constant? constant) { - var expr = Create(dmObject, proc, expression); - return expr.TryAsConstant(out constant); - } - // Attempt to convert this expression into a Constant expression - public virtual bool TryAsConstant([NotNullWhen(true)] out Expressions.Constant? constant) { + public virtual bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { constant = null; return false; } // Attempt to create a json-serializable version of this expression - public virtual bool TryAsJsonRepresentation(out object? json) { + public virtual bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { json = null; return false; } // Emits code that pushes the result of this expression to the proc's stack // May throw if this expression is unable to be pushed to the stack - public abstract void EmitPushValue(DMObject dmObject, DMProc proc); + public abstract void EmitPushValue(ExpressionContext ctx); public enum ShortCircuitMode { // If a dereference is short-circuited due to a null conditional, the short-circuit label should be jumped to with null NOT on top of the stack @@ -55,8 +39,8 @@ public enum ShortCircuitMode { // Emits a reference that is to be used in an opcode that assigns/gets a value // May throw if this expression is unable to be referenced // The emitted code will jump to endLabel after pushing `null` to the stack in the event of a short-circuit - public virtual DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { - DMCompiler.Emit(WarningCode.BadExpression, Location, "attempt to reference r-value"); + public virtual DMReference EmitReference(ExpressionContext ctx, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "attempt to reference r-value"); return DMReference.Invalid; } @@ -64,7 +48,7 @@ public virtual DMReference EmitReference(DMObject dmObject, DMProc proc, string /// Gets the canonical name of the expression if it exists. ///
/// The name of the expression, or null if it does not have one. - public virtual string? GetNameof(DMObject dmObject) => null; + public virtual string? GetNameof(ExpressionContext ctx) => null; /// /// Determines whether the expression returns an ambiguous path. @@ -79,76 +63,21 @@ public virtual DMReference EmitReference(DMObject dmObject, DMProc proc, string // (a, b, c, ...) // This isn't an expression, it's just a helper class for working with argument lists -internal sealed class ArgumentList { - public readonly (string? Name, DMExpression Expr)[] Expressions; +internal sealed class ArgumentList(Location location, (string? Name, DMExpression Expr)[] expressions, bool isKeyed) { + public readonly (string? Name, DMExpression Expr)[] Expressions = expressions; public int Length => Expressions.Length; - public Location Location; - - // Whether or not this has named arguments - private readonly bool _isKeyed; - - public ArgumentList(Location location, DMObject dmObject, DMProc proc, DMASTCallParameter[]? arguments, DreamPath? inferredPath = null) { - Location = location; - if (arguments == null) { - Expressions = Array.Empty<(string?, DMExpression)>(); - return; - } - - Expressions = new (string?, DMExpression)[arguments.Length]; - - int idx = 0; - foreach(var arg in arguments) { - var value = DMExpression.Create(dmObject, proc, arg.Value, inferredPath); - var key = (arg.Key != null) ? DMExpression.Create(dmObject, proc, arg.Key, inferredPath) : null; - int argIndex = idx++; - string? name = null; - - switch (key) { - case Expressions.String keyStr: - name = keyStr.Value; - break; - case Expressions.Number keyNum: - //Replaces an ordered argument - var newIdx = (int)keyNum.Value - 1; - - if (newIdx == argIndex) { - DMCompiler.Emit(WarningCode.PointlessPositionalArgument, key.Location, - $"The argument at index {argIndex + 1} is a positional argument with a redundant index (\"{argIndex + 1} = value\" at argument {argIndex + 1}). This does not function like a named argument and is likely a mistake."); - } - - argIndex = newIdx; - break; - case Expressions.Resource _: - case Expressions.ConstantPath _: - //The key becomes the value - value = key; - break; - - default: - if (key != null) { - DMCompiler.Emit(WarningCode.InvalidArgumentKey, key.Location, "Invalid argument key"); - } - - break; - } - - if (name != null) - _isKeyed = true; - - Expressions[argIndex] = (name, value); - } - } + public Location Location = location; - public (DMCallArgumentsType Type, int StackSize) EmitArguments(DMObject dmObject, DMProc proc, DMProc? targetProc) { + public (DMCallArgumentsType Type, int StackSize) EmitArguments(ExpressionContext ctx, DMProc? targetProc) { if (Expressions.Length == 0) { return (DMCallArgumentsType.None, 0); } - if (Expressions[0].Expr is Expressions.Arglist arglist) { + if (Expressions[0].Expr is Arglist arglist) { if (Expressions[0].Name != null) - DMCompiler.Emit(WarningCode.BadArgument, arglist.Location, "arglist cannot be a named argument"); + ctx.Compiler.Emit(WarningCode.BadArgument, arglist.Location, "arglist cannot be a named argument"); - arglist.EmitPushArglist(dmObject, proc); + arglist.EmitPushArglist(ctx); return (DMCallArgumentsType.FromArgumentList, 1); } @@ -158,24 +87,24 @@ public ArgumentList(Location location, DMObject dmObject, DMProc proc, DMASTCall (string? name, DMExpression expr) = Expressions[index]; if (targetProc != null) - VerifyArgType(targetProc, index, name, expr); + VerifyArgType(ctx.Compiler, targetProc, index, name, expr); - if (_isKeyed) { + if (isKeyed) { if (name != null) { - proc.PushString(name); + ctx.Proc.PushString(name); } else { - proc.PushNull(); + ctx.Proc.PushNull(); } } - expr.EmitPushValue(dmObject, proc); - stackCount += _isKeyed ? 2 : 1; + expr.EmitPushValue(ctx); + stackCount += isKeyed ? 2 : 1; } - return (_isKeyed ? DMCallArgumentsType.FromStackKeyed : DMCallArgumentsType.FromStack, stackCount); + return (isKeyed ? DMCallArgumentsType.FromStackKeyed : DMCallArgumentsType.FromStack, stackCount); } - private static void VerifyArgType(DMProc targetProc, int index, string? name, DMExpression expr) { + private void VerifyArgType(DMCompiler compiler, DMProc targetProc, int index, string? name, DMExpression expr) { // TODO: See if the static typechecking can be improved // Also right now we don't care if the arg is Anything // TODO: Make a separate "UnsetStaticType" pragma for whether we should care if it's Anything @@ -193,7 +122,7 @@ private static void VerifyArgType(DMProc targetProc, int index, string? name, DM if (param == null) { // TODO: Remove this check once variadic args are properly supported if (targetProc.Name != "animate" && index < targetProc.Parameters.Count) { - DMCompiler.Emit(WarningCode.InvalidVarType, expr.Location, + compiler.Emit(WarningCode.InvalidVarType, expr.Location, $"{targetProc.Name}(...): Unknown argument {(name is null ? $"at index {index}" : $"\"{name}\"")}, typechecking failed"); } @@ -202,9 +131,17 @@ private static void VerifyArgType(DMProc targetProc, int index, string? name, DM DMComplexValueType paramType = param.ExplicitValueType ?? DMValueType.Anything; - if (!expr.ValType.IsAnything && !paramType.MatchesType(expr.ValType)) { - DMCompiler.Emit(WarningCode.InvalidVarType, expr.Location, + if (!expr.ValType.IsAnything && !paramType.MatchesType(compiler, expr.ValType)) { + compiler.Emit(WarningCode.InvalidVarType, expr.Location, $"{targetProc.Name}(...) argument \"{param.Name}\": Invalid var value type {expr.ValType}, expected {paramType}"); } } } + +internal readonly struct ExpressionContext(DMCompiler compiler, DMObject type, DMProc proc) { + public readonly DMCompiler Compiler = compiler; + public readonly DMObject Type = type; + public readonly DMProc Proc = proc; + + public DMObjectTree ObjectTree => Compiler.DMObjectTree; +} diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs index f3841de6f5..d48d84eb6f 100644 --- a/DMCompiler/DM/DMObject.cs +++ b/DMCompiler/DM/DMObject.cs @@ -1,5 +1,4 @@ using DMCompiler.Bytecode; -using DMCompiler.Compiler; using DMCompiler.Json; namespace DMCompiler.DM; @@ -9,35 +8,33 @@ namespace DMCompiler.DM; /// but rather stores the compile-time information necessary to describe a certain object definition,
/// including its procs, vars, path, parent, etc. /// -internal sealed class DMObject { - public int Id; - public DreamPath Path; - public DMObject? Parent; - public Dictionary> Procs = new(); - public Dictionary Variables = new(); - /// It's OK if the override var is not literally the exact same object as what it overrides. - public Dictionary VariableOverrides = new(); - public Dictionary GlobalVariables = new(); - /// A list of var and verb initializations implicitly done before the user's New() is called. - public HashSet ConstVariables = new(); - public HashSet TmpVariables = new(); - public List InitializationProcExpressions = new(); +internal sealed class DMObject(DMCompiler compiler, int id, DreamPath path, DMObject? parent) { + public readonly int Id = id; + public DreamPath Path = path; + public DMObject? Parent = parent; + public readonly Dictionary> Procs = new(); + public readonly Dictionary Variables = new(); + public readonly Dictionary GlobalVariables = new(); + public readonly Dictionary VariableOverrides = new(); + public readonly HashSet TmpVariables = new(); + public readonly HashSet ConstVariables = new(); public int? InitializationProc; + /// A list of var and verb initializations implicitly done before the user's New() is called. + public readonly List InitializationProcExpressions = new(); + public bool IsRoot => Path == DreamPath.Root; private List? _verbs; - public DMObject(int id, DreamPath path, DMObject? parent) { - Id = id; - Path = path; - Parent = parent; - } - - public void AddProc(string name, DMProc proc) { - if (!Procs.ContainsKey(name)) Procs.Add(name, new List(1)); + public void AddProc(DMProc proc, bool forceFirst = false) { + if (!Procs.ContainsKey(proc.Name)) + Procs.Add(proc.Name, new List(1)); - Procs[name].Add(proc.Id); + if (forceFirst) + Procs[proc.Name].Insert(0, proc.Id); + else + Procs[proc.Name].Add(proc.Id); } /// @@ -86,7 +83,10 @@ public bool HasProc(string name) { return Parent?.HasProc(name) ?? false; } - public bool HasProcNoInheritance(string name) { + /// + /// Whether a proc was defined on this type. Inheritance is not considered. + /// + public bool OwnsProc(string name) { return Procs.ContainsKey(name); } @@ -95,12 +95,12 @@ public bool HasProcNoInheritance(string name) { } public DMComplexValueType? GetProcReturnTypes(string name) { - if (this == DMObjectTree.Root && DMObjectTree.TryGetGlobalProc(name, out var globalProc)) + if (this == compiler.DMObjectTree.Root && compiler.DMObjectTree.TryGetGlobalProc(name, out var globalProc)) return globalProc.RawReturnTypes; if (GetProcs(name) is not { } procs) return Parent?.GetProcReturnTypes(name); - var proc = DMObjectTree.AllProcs[procs[0]]; + var proc = compiler.DMObjectTree.AllProcs[procs[0]]; if ((proc.Attributes & ProcAttributes.IsOverride) != 0) return Parent?.GetProcReturnTypes(name) ?? DMValueType.Anything; @@ -108,15 +108,29 @@ public bool HasProcNoInheritance(string name) { } public void AddVerb(DMProc verb) { - _verbs ??= new(); + if (!compiler.Settings.NoStandard && !IsSubtypeOf(DreamPath.Atom) && !IsSubtypeOf(DreamPath.Client)) + return; + + _verbs ??= []; _verbs.Add(verb); } - public DMVariable CreateGlobalVariable(DreamPath? type, string name, bool isConst, DMComplexValueType? valType = null) { - int id = DMObjectTree.CreateGlobal(out DMVariable global, type, name, isConst, valType ?? DMValueType.Anything); + public void AddVariable(DMVariable variable) { + Variables[variable.Name] = variable; + + if (variable.IsConst) + ConstVariables.Add(variable.Name); + if (variable.IsTmp) + TmpVariables.Add(variable.Name); + } + + public void AddGlobalVariable(DMVariable global, int id) { + GlobalVariables[global.Name] = id; - GlobalVariables[name] = id; - return global; + if (global.IsConst) + ConstVariables.Add(global.Name); + if (global.IsTmp) + TmpVariables.Add(global.Name); } /// @@ -131,29 +145,23 @@ public DMVariable CreateGlobalVariable(DreamPath? type, string name, bool isCons return Parent?.GetGlobalVariableId(name); } - public DMVariable? GetGlobalVariable(string name) { - int? id = GetGlobalVariableId(name); - - return (id == null) ? null : DMObjectTree.Globals[id.Value]; - } - public DMComplexValueType GetReturnType(string name) { var procId = GetProcs(name)?[^1]; - return procId is null ? DMValueType.Anything : DMObjectTree.AllProcs[procId.Value].ReturnTypes; + return procId is null ? DMValueType.Anything : compiler.DMObjectTree.AllProcs[procId.Value].ReturnTypes; } public void CreateInitializationProc() { if (InitializationProcExpressions.Count <= 0 || InitializationProc != null) return; - var init = DMObjectTree.CreateDMProc(this, null); + var init = compiler.DMObjectTree.CreateDMProc(this, null); InitializationProc = init.Id; init.Call(DMReference.SuperProc, DMCallArgumentsType.None, 0); foreach (DMExpression expression in InitializationProcExpressions) { init.DebugSource(expression.Location); - expression.EmitPushValue(this, init); + expression.EmitPushValue(new(compiler, this, init)); } } @@ -167,14 +175,14 @@ public DreamTypeJson CreateJsonRepresentation() { typeJson.Variables = new Dictionary(); foreach (KeyValuePair variable in Variables) { - if (!variable.Value.TryAsJsonRepresentation(out var valueJson)) + if (!variable.Value.TryAsJsonRepresentation(compiler, out var valueJson)) throw new Exception($"Failed to serialize {Path}.{variable.Key}"); typeJson.Variables.Add(variable.Key, valueJson); } foreach (KeyValuePair variable in VariableOverrides) { - if (!variable.Value.TryAsJsonRepresentation(out var valueJson)) + if (!variable.Value.TryAsJsonRepresentation(compiler, out var valueJson)) throw new Exception($"Failed to serialize {Path}.{variable.Key}"); typeJson.Variables[variable.Key] = valueJson; @@ -198,7 +206,7 @@ public DreamTypeJson CreateJsonRepresentation() { } if (Procs.Count > 0) { - typeJson.Procs = new List>(Procs.Values); + typeJson.Procs = [..Procs.Values]; } if (_verbs != null) { diff --git a/DMCompiler/DM/DMObjectTree.cs b/DMCompiler/DM/DMObjectTree.cs index 9b08ac0ad1..3dad425614 100644 --- a/DMCompiler/DM/DMObjectTree.cs +++ b/DMCompiler/DM/DMObjectTree.cs @@ -1,74 +1,40 @@ using System.Diagnostics.CodeAnalysis; -using DMCompiler.Bytecode; using DMCompiler.Compiler; using DMCompiler.Compiler.DM.AST; using DMCompiler.Json; namespace DMCompiler.DM; -internal static class DMObjectTree { - public static readonly List AllObjects = new(); - public static readonly List AllProcs = new(); +internal class DMObjectTree(DMCompiler compiler) { + public readonly List AllObjects = new(); + public readonly List AllProcs = new(); //TODO: These don't belong in the object tree - public static readonly List Globals = new(); - public static readonly Dictionary GlobalProcs = new(); - /// - /// Used to keep track of when we see a /proc/foo() or whatever, so that duplicates or missing definitions can be discovered, - /// even as GlobalProcs keeps clobbering old global proc overrides/definitions. - /// - public static readonly HashSet SeenGlobalProcDefinition = new(); - public static readonly List StringTable = new(); - public static DMProc GlobalInitProc = default!; // Initialized by Reset() (called in the static initializer) - public static readonly HashSet Resources = new(); + public readonly List Globals = new(); + public readonly Dictionary GlobalProcs = new(); + public readonly List StringTable = new(); + public readonly HashSet Resources = new(); - public static DMObject Root => GetDMObject(DreamPath.Root)!; + public DMObject Root => GetOrCreateDMObject(DreamPath.Root); - private static readonly Dictionary StringToStringId = new(); - private static readonly List<(int GlobalId, DMExpression Value)> _globalInitAssigns = new(); + private readonly Dictionary _stringToStringId = new(); + private readonly Dictionary _pathToTypeId = new(); + private int _dmObjectIdCounter; + private int _dmProcIdCounter; - private static readonly Dictionary _pathToTypeId = new(); - private static int _dmObjectIdCounter; - private static int _dmProcIdCounter; - - static DMObjectTree() { - Reset(); - } - - /// - /// A thousand curses upon you if you add a new member to this thing without deleting it here. - /// - public static void Reset() { - AllObjects.Clear(); - AllProcs.Clear(); - - Globals.Clear(); - GlobalProcs.Clear(); - SeenGlobalProcDefinition.Clear(); - StringTable.Clear(); - StringToStringId.Clear(); - Resources.Clear(); - - _globalInitAssigns.Clear(); - _pathToTypeId.Clear(); - _dmObjectIdCounter = 0; - _dmProcIdCounter = 0; - GlobalInitProc = new(-1, Root, null); - } - - public static int AddString(string value) { - if (!StringToStringId.TryGetValue(value, out var stringId)) { + public int AddString(string value) { + if (!_stringToStringId.TryGetValue(value, out var stringId)) { stringId = StringTable.Count; StringTable.Add(value); - StringToStringId.Add(value, stringId); + _stringToStringId.Add(value, stringId); } return stringId; } - public static DMProc CreateDMProc(DMObject dmObject, DMASTProcDefinition? astDefinition) { - DMProc dmProc = new DMProc(_dmProcIdCounter++, dmObject, astDefinition); + public DMProc CreateDMProc(DMObject dmObject, DMASTProcDefinition? astDefinition) { + DMProc dmProc = new DMProc(compiler, _dmProcIdCounter++, dmObject, astDefinition); AllProcs.Add(dmProc); return dmProc; @@ -78,21 +44,23 @@ public static DMProc CreateDMProc(DMObject dmObject, DMASTProcDefinition? astDef /// Returns the "New()" DMProc for a given object type ID /// /// - public static DMProc GetNewProc(int id) { + public DMProc? GetNewProc(int id) { var obj = AllObjects[id]; - var targetProc = obj!.GetProcs("New")[0]; - return AllProcs[targetProc]; + var procs = obj.GetProcs("New"); + + if (procs != null) + return AllProcs[procs[0]]; + else + return null; } - public static DMObject? GetDMObject(DreamPath path, bool createIfNonexistent = true) { - if (_pathToTypeId.TryGetValue(path, out int typeId)) { - return AllObjects[typeId]; - } - if (!createIfNonexistent) return null; + public DMObject GetOrCreateDMObject(DreamPath path) { + if (TryGetDMObject(path, out var dmObject)) + return dmObject; DMObject? parent = null; if (path.Elements.Length > 1) { - parent = GetDMObject(path.FromElements(0, -2)); // Create all parent classes as dummies, if we're being dummy-created too + parent = GetOrCreateDMObject(path.FromElements(0, -2)); // Create all parent classes as dummies, if we're being dummy-created too } else if (path.Elements.Length == 1) { switch (path.LastElement) { case "client": @@ -100,10 +68,10 @@ public static DMProc GetNewProc(int id) { case "list": case "savefile": case "world": - parent = GetDMObject(DreamPath.Root); + parent = GetOrCreateDMObject(DreamPath.Root); break; default: - parent = GetDMObject(DMCompiler.Settings.NoStandard ? DreamPath.Root : DreamPath.Datum); + parent = GetOrCreateDMObject(compiler.Settings.NoStandard ? DreamPath.Root : DreamPath.Datum); break; } } @@ -111,13 +79,23 @@ public static DMProc GetNewProc(int id) { if (path != DreamPath.Root && parent == null) // Parent SHOULD NOT be null here! (unless we're root lol) throw new Exception($"Type {path} did not have a parent"); - DMObject dmObject = new DMObject(_dmObjectIdCounter++, path, parent); + dmObject = new DMObject(compiler, _dmObjectIdCounter++, path, parent); AllObjects.Add(dmObject); _pathToTypeId[path] = dmObject.Id; return dmObject; } - public static bool TryGetGlobalProc(string name, [NotNullWhen(true)] out DMProc? proc) { + public bool TryGetDMObject(DreamPath path, [NotNullWhen(true)] out DMObject? dmObject) { + if (_pathToTypeId.TryGetValue(path, out int typeId)) { + dmObject = AllObjects[typeId]; + return true; + } + + dmObject = null; + return false; + } + + public bool TryGetGlobalProc(string name, [NotNullWhen(true)] out DMProc? proc) { if (!GlobalProcs.TryGetValue(name, out var id)) { proc = null; return false; @@ -128,12 +106,12 @@ public static bool TryGetGlobalProc(string name, [NotNullWhen(true)] out DMProc? } /// True if the path exists, false if not. Keep in mind though that we may just have not found this object path yet while walking in ObjectBuilder. - public static bool TryGetTypeId(DreamPath path, out int typeId) { + public bool TryGetTypeId(DreamPath path, out int typeId) { return _pathToTypeId.TryGetValue(path, out typeId); } // TODO: This is all so snowflake and needs redone - public static DreamPath? UpwardSearch(DreamPath path, DreamPath search) { + public DreamPath? UpwardSearch(DreamPath path, DreamPath search) { bool requireProcElement = search.Type == DreamPath.PathType.Absolute; string? searchingProcName = null; @@ -184,7 +162,7 @@ public static bool TryGetTypeId(DreamPath path, out int typeId) { return null; } - public static int CreateGlobal(out DMVariable global, DreamPath? type, string name, bool isConst, DMComplexValueType valType) { + public int CreateGlobal(out DMVariable global, DreamPath? type, string name, bool isConst, DMComplexValueType valType) { int id = Globals.Count; global = new DMVariable(type, name, true, isConst, false, valType); @@ -192,29 +170,16 @@ public static int CreateGlobal(out DMVariable global, DreamPath? type, string na return id; } - public static void AddGlobalProc(DMProc proc) { - // Said in this way so it clobbers previous definitions of this global proc (the ..() stuff doesn't work with glob procs) - GlobalProcs[proc.Name] = proc.Id; - } - - public static void AddGlobalInitAssign(int globalId, DMExpression value) { - _globalInitAssigns.Add( (globalId, value) ); - } - - public static void CreateGlobalInitProc() { - if (_globalInitAssigns.Count == 0) return; - - foreach (var assign in _globalInitAssigns) { - GlobalInitProc.DebugSource(assign.Value.Location); - - assign.Value.EmitPushValue(Root, GlobalInitProc); - GlobalInitProc.Assign(DMReference.CreateGlobal(assign.GlobalId)); + public void AddGlobalProc(DMProc proc) { + if (GlobalProcs.ContainsKey(proc.Name)) { + compiler.Emit(WarningCode.DuplicateProcDefinition, proc.Location, $"Global proc {proc.Name} is already defined"); + return; } - GlobalInitProc.ResolveLabels(); + GlobalProcs[proc.Name] = proc.Id; } - public static (DreamTypeJson[], ProcDefinitionJson[]) CreateJsonRepresentation() { + public (DreamTypeJson[], ProcDefinitionJson[]) CreateJsonRepresentation() { DreamTypeJson[] types = new DreamTypeJson[AllObjects.Count]; ProcDefinitionJson[] procs = new ProcDefinitionJson[AllProcs.Count]; diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index a84715cfc1..376fc8a2bd 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -29,7 +29,7 @@ public sealed class LocalConstVariable(string name, int id, DreamPath? type, Con } public class CodeLabel { - private static int _idCounter = 0; + private static int _idCounter; public readonly long AnnotatedByteOffset; public readonly int Id; public readonly string Name; @@ -63,10 +63,10 @@ public DMProcScope(DMProcScope? parentScope) { } public string Name => _astDefinition?.Name ?? ""; + public bool IsVerb => _astDefinition?.IsVerb ?? false; public List Parameters = new(); public Location Location; public ProcAttributes Attributes; - public bool IsVerb = false; public readonly int Id; public readonly Dictionary GlobalVariables = new(); @@ -76,6 +76,7 @@ public DMProcScope(DMProcScope? parentScope) { public string? VerbDesc; public sbyte Invisibility; + private readonly DMCompiler _compiler; private readonly DMObject _dmObject; private readonly DMASTProcDefinition? _astDefinition; private readonly Stack _pendingLabelReferences = new(); @@ -96,11 +97,13 @@ public DMProcScope(DMProcScope? parentScope) { public DMComplexValueType ReturnTypes => _dmObject.GetProcReturnTypes(Name) ?? DMValueType.Anything; public long Position => AnnotatedBytecode.Position; - public AnnotatedByteCodeWriter AnnotatedBytecode = new(); + public readonly AnnotatedByteCodeWriter AnnotatedBytecode; private Location _writerLocation; - public DMProc(int id, DMObject dmObject, DMASTProcDefinition? astDefinition) { + public DMProc(DMCompiler compiler, int id, DMObject dmObject, DMASTProcDefinition? astDefinition) { + AnnotatedBytecode = new(compiler); + _compiler = compiler; Id = id; _dmObject = dmObject; _astDefinition = astDefinition; @@ -129,10 +132,10 @@ private void DeallocLocalVariables(int amount) { } public void Compile() { - DMCompiler.VerbosePrint($"Compiling proc {_dmObject?.Path.ToString() ?? "Unknown"}.{Name}()"); + _compiler.VerbosePrint($"Compiling proc {_dmObject?.Path.ToString() ?? "Unknown"}.{Name}()"); if (_astDefinition is not null) { // It's null for initialization procs - new DMProcBuilder(_dmObject, this).ProcessProcDefinition(_astDefinition); + new DMProcBuilder(_compiler, _dmObject, this).ProcessProcDefinition(_astDefinition); } } @@ -140,37 +143,37 @@ public void ValidateReturnType(DMExpression expr) { var type = expr.ValType; var returnTypes = _dmObject.GetProcReturnTypes(Name)!.Value; if ((returnTypes.Type & (DMValueType.Color | DMValueType.File | DMValueType.Message)) != 0) { - DMCompiler.Emit(WarningCode.UnsupportedTypeCheck, expr.Location, "color, message, and file return types are currently unsupported."); + _compiler.Emit(WarningCode.UnsupportedTypeCheck, expr.Location, "color, message, and file return types are currently unsupported."); return; } var splitter = _astDefinition?.IsOverride ?? false ? "/" : "/proc/"; // We couldn't determine the expression's return type for whatever reason if (type.IsAnything) { - if (DMCompiler.Settings.SkipAnythingTypecheck) + if (_compiler.Settings.SkipAnythingTypecheck) return; switch (expr) { case ProcCall: - DMCompiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{Name}(): Called proc does not have a return type set, expected {ReturnTypes}."); + _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{Name}(): Called proc does not have a return type set, expected {ReturnTypes}."); break; case Local: - DMCompiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{Name}(): Cannot determine return type of non-constant expression, expected {ReturnTypes}. Consider making this variable constant or adding an explicit \"as {ReturnTypes}\""); + _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{Name}(): Cannot determine return type of non-constant expression, expected {ReturnTypes}. Consider making this variable constant or adding an explicit \"as {ReturnTypes}\""); break; default: - DMCompiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{Name}(): Cannot determine return type of expression \"{expr}\", expected {ReturnTypes}. Consider reporting this as a bug on OpenDream's GitHub."); + _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}.{Name}(): Cannot determine return type of expression \"{expr}\", expected {ReturnTypes}. Consider reporting this as a bug on OpenDream's GitHub."); break; } - } else if (!ReturnTypes.MatchesType(type)) { // We could determine the return types but they don't match - DMCompiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}{splitter}{Name}(): Invalid return type {type}, expected {ReturnTypes}"); + } else if (!ReturnTypes.MatchesType(_compiler, type)) { // We could determine the return types but they don't match + _compiler.Emit(WarningCode.InvalidReturnType, expr.Location, $"{_dmObject?.Path.ToString() ?? "Unknown"}{splitter}{Name}(): Invalid return type {type}, expected {ReturnTypes}"); } } public ProcDefinitionJson GetJsonRepresentation() { var optimizer = new BytecodeOptimizer(); - var serializer = new AnnotatedBytecodeSerializer(); + var serializer = new AnnotatedBytecodeSerializer(_compiler); - optimizer.Optimize(AnnotatedBytecode.GetAnnotatedBytecode()); + optimizer.Optimize(_compiler, AnnotatedBytecode.GetAnnotatedBytecode()); List? arguments = null; if (_parameters.Count > 0) { @@ -182,8 +185,7 @@ public ProcDefinitionJson GetJsonRepresentation() { if (parameter.Type is not { } typePath) { argumentType = DMValueType.Anything; } else { - var type = DMObjectTree.GetDMObject(typePath, false); - + _compiler.DMObjectTree.TryGetDMObject(typePath, out var type); argumentType = type?.GetDMValueType() ?? DMValueType.Anything; } } @@ -229,11 +231,8 @@ public void WaitFor(bool waitFor) { } } - public DMVariable CreateGlobalVariable(DreamPath? type, string name, bool isConst, out int id) { - id = DMObjectTree.CreateGlobal(out DMVariable global, type, name, isConst, DMValueType.Anything); - - GlobalVariables[name] = id; - return global; + public void AddGlobalVariable(DMVariable global, int id) { + GlobalVariables[global.Name] = id; } public int? GetGlobalVariableId(string name) { @@ -246,7 +245,7 @@ public DMVariable CreateGlobalVariable(DreamPath? type, string name, bool isCons public void AddParameter(string name, DMComplexValueType? valueType, DreamPath? type) { if (_parameters.ContainsKey(name)) { - DMCompiler.Emit(WarningCode.DuplicateVariable, _astDefinition.Location, $"Duplicate argument \"{name}\""); + _compiler.Emit(WarningCode.DuplicateVariable, _astDefinition.Location, $"Duplicate argument \"{name}\""); } else { Parameters.Add(name); _parameters.Add(name, new LocalVariable(name, _parameters.Count, true, type, valueType)); @@ -271,7 +270,7 @@ public bool TryGetParameterAtIndex(int index, [NotNullWhen(true)] out LocalVaria public CodeLabel? TryAddCodeLabel(string name) { if (_scopes.Peek().LocalCodeLabels.ContainsKey(name)) { - DMCompiler.Emit(WarningCode.DuplicateVariable, Location, $"A label with the name \"{name}\" already exists"); + _compiler.Emit(WarningCode.DuplicateVariable, Location, $"A label with the name \"{name}\" already exists"); return null; } @@ -333,7 +332,7 @@ public void DebugSource(Location location) { // Only write the source file if it has changed if (_lastSourceFile != sourceFile) { - sourceInfo.File = DMObjectTree.AddString(sourceFile); + sourceInfo.File = _compiler.DMObjectTree.AddString(sourceFile); } else if (_sourceInfo.Count > 0 && sourceInfo.Line == _sourceInfo[^1].Line) { // Don't need to write this source info if it's the same source & line as the last return; @@ -376,7 +375,7 @@ public void Enumerate(DMReference reference) { WriteReference(reference); WriteLabel($"{peek}_end"); } else { - DMCompiler.ForcedError(Location, "Cannot peek empty loop stack"); + _compiler.ForcedError(Location, "Cannot peek empty loop stack"); } } @@ -386,7 +385,7 @@ public void EnumerateNoAssign() { WriteEnumeratorId(_enumeratorIdCounter - 1); WriteLabel($"{peek}_end"); } else { - DMCompiler.ForcedError(Location, "Cannot peek empty loop stack"); + _compiler.ForcedError(Location, "Cannot peek empty loop stack"); } } @@ -401,6 +400,12 @@ public void CreateList(int size) { WriteListSize(size); } + public void CreateMultidimensionalList(int dimensionCount) { + ResizeStack(-(dimensionCount - 1)); // Pops the amount of dimensions, then pushes the list + WriteOpcode(DreamProcOpcode.CreateMultidimensionalList); + WriteListSize(dimensionCount); + } + public void CreateAssociativeList(int size) { ResizeStack(-(size * 2 - 1)); //Shrinks by twice the size of the list, grows by 1 WriteOpcode(DreamProcOpcode.CreateAssociativeList); @@ -427,8 +432,8 @@ public void BackgroundSleep() { // TODO This seems like a bad way to handle background, doesn't it? if ((Attributes & ProcAttributes.Background) == ProcAttributes.Background) { - if (!DMObjectTree.TryGetGlobalProc("sleep", out var sleepProc)) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, "Cannot do a background sleep without a sleep proc"); + if (!_compiler.DMObjectTree.TryGetGlobalProc("sleep", out var sleepProc)) { + _compiler.Emit(WarningCode.ItemDoesntExist, Location, "Cannot do a background sleep without a sleep proc"); return; } @@ -447,7 +452,7 @@ public void LoopEnd() { if (_loopStack?.TryPop(out var pop) ?? false) { AddLabel(pop + "_end"); } else { - DMCompiler.ForcedError(Location, "Cannot pop empty loop stack"); + _compiler.ForcedError(Location, "Cannot pop empty loop stack"); } EndScope(); @@ -503,14 +508,14 @@ public void Break(DMASTIdentifier? label = null) { if (label is not null) { var codeLabel = (GetCodeLabel(label.Identifier, _scopes.Peek())?.LabelName ?? label.Identifier + "_codelabel"); if (!LabelExists(codeLabel)) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, label.Location, $"Unknown label {label.Identifier}"); + _compiler.Emit(WarningCode.ItemDoesntExist, label.Location, $"Unknown label {label.Identifier}"); } Jump(codeLabel + "_end"); } else if (_loopStack?.TryPeek(out var peek) ?? false) { Jump(peek + "_end"); } else { - DMCompiler.ForcedError(Location, "Cannot peek empty loop stack"); + _compiler.ForcedError(Location, "Cannot peek empty loop stack"); } } @@ -518,7 +523,7 @@ public void BreakIfFalse() { if (_loopStack?.TryPeek(out var peek) ?? false) { JumpIfFalse($"{peek}_end"); } else { - DMCompiler.ForcedError(Location, "Cannot peek empty loop stack"); + _compiler.ForcedError(Location, "Cannot peek empty loop stack"); } } @@ -531,7 +536,7 @@ public void Continue(DMASTIdentifier? label = null) { label.Identifier + "_codelabel" ); if (!LabelExists(codeLabel)) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, label.Location, $"Unknown label {label.Identifier}"); + _compiler.Emit(WarningCode.ItemDoesntExist, label.Location, $"Unknown label {label.Identifier}"); } var labelList = GetLabels().Keys.ToList(); @@ -551,7 +556,7 @@ public void Continue(DMASTIdentifier? label = null) { if (_loopStack?.TryPeek(out var peek) ?? false) { Jump(peek + "_continue"); } else { - DMCompiler.ForcedError(Location, "Cannot peek empty loop stack"); + _compiler.ForcedError(Location, "Cannot peek empty loop stack"); } } } diff --git a/DMCompiler/DM/DMValueType.cs b/DMCompiler/DM/DMValueType.cs index e521524bb7..61f23652aa 100644 --- a/DMCompiler/DM/DMValueType.cs +++ b/DMCompiler/DM/DMValueType.cs @@ -28,6 +28,7 @@ public enum DMValueType { //Byond here be dragons Unimplemented = 0x4000, // Marks that a method or property is not implemented. Throws a compiler warning if accessed. CompiletimeReadonly = 0x8000, // Marks that a property can only ever be read from, never written to. This is a const-ier version of const, for certain standard values like list.type + NoConstFold = 0x10000 // Marks that a const var cannot be const-folded during compile } /// @@ -56,12 +57,10 @@ public bool MatchesType(DMValueType type) { return IsAnything || (Type & type) != 0; } - public bool MatchesType(DMComplexValueType type) { + internal bool MatchesType(DMCompiler compiler, DMComplexValueType type) { if (IsPath && type.IsPath) { - var dmObject = DMObjectTree.GetDMObject(type.TypePath!.Value, false); - - // Allow subtypes - if (dmObject?.IsSubtypeOf(TypePath!.Value) is true) + if (compiler.DMObjectTree.TryGetDMObject(type.TypePath!.Value, out var dmObject) && + dmObject.IsSubtypeOf(TypePath!.Value)) // Allow subtypes return true; } diff --git a/DMCompiler/DM/DMVariable.cs b/DMCompiler/DM/DMVariable.cs index 86510b08d1..2ff3b649e8 100644 --- a/DMCompiler/DM/DMVariable.cs +++ b/DMCompiler/DM/DMVariable.cs @@ -14,6 +14,9 @@ internal sealed class DMVariable { public DMExpression? Value; public DMComplexValueType ValType; + public bool CanConstFold => (IsConst || ValType.Type.HasFlag(DMValueType.CompiletimeReadonly)) && + !ValType.Type.HasFlag(DMValueType.NoConstFold); + public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, bool isTmp, DMComplexValueType? valType = null) { Type = type; Name = name; @@ -24,23 +27,17 @@ public DMVariable(DreamPath? type, string name, bool isGlobal, bool isConst, boo ValType = valType ?? DMValueType.Anything; } - /// - /// This is a copy-on-write proc used to set the DMVariable to a constant value.
- /// In some contexts, doing so would clobber pre-existing constants,
- /// and so this sometimes creates a copy of , with the new constant value. - ///
- public DMVariable WriteToValue(Expressions.Constant value) { - if (Value == null) { - Value = value; - return this; - } - - DMVariable clone = new DMVariable(Type, Name, IsGlobal, IsConst, IsTmp, ValType); - clone.Value = value; - return clone; + public DMVariable(DMVariable copyFrom) { + Type = copyFrom.Type; + Name = copyFrom.Name; + IsGlobal = copyFrom.IsGlobal; + IsConst = copyFrom.IsConst; + IsTmp = copyFrom.IsTmp; + Value = copyFrom.Value; + ValType = copyFrom.ValType; } - public bool TryAsJsonRepresentation([NotNullWhen(true)] out object? valueJson) { - return Value.TryAsJsonRepresentation(out valueJson); + public bool TryAsJsonRepresentation(DMCompiler compiler, [NotNullWhen(true)] out object? valueJson) { + return Value.TryAsJsonRepresentation(compiler, out valueJson); } } diff --git a/DMCompiler/DM/Expressions/Binary.cs b/DMCompiler/DM/Expressions/Binary.cs index 1b05d20317..0d9420faeb 100644 --- a/DMCompiler/DM/Expressions/Binary.cs +++ b/DMCompiler/DM/Expressions/Binary.cs @@ -13,10 +13,11 @@ internal abstract class BinaryOp(Location location, DMExpression lhs, DMExpressi } #region Simple + // x + y internal sealed class Add(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -33,17 +34,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.Add(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.Add(); } } // x - y internal sealed class Subtract(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -58,17 +59,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.Subtract(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.Subtract(); } } // x * y internal sealed class Multiply(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -83,17 +84,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.Multiply(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.Multiply(); } } // x / y internal sealed class Divide(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -108,17 +109,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.Divide(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.Divide(); } } // x % y internal sealed class Modulo(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -133,17 +134,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.Modulus(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.Modulus(); } } // x %% y internal sealed class ModuloModulo(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -161,17 +162,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.ModulusModulus(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.ModulusModulus(); } } // x ** y internal sealed class Power(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -186,17 +187,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.Power(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.Power(); } } // x << y internal sealed class LeftShift(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -211,17 +212,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.BitShiftLeft(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.BitShiftLeft(); } } // x >> y internal sealed class RightShift(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -236,10 +237,10 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.BitShiftRight(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.BitShiftRight(); } } @@ -247,8 +248,8 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class BinaryAnd(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { public override bool PathIsFuzzy => true; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -263,17 +264,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.BinaryAnd(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.BinaryAnd(); } } // x ^ y internal sealed class BinaryXor(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -288,17 +289,17 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.BinaryXor(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.BinaryXor(); } } // x | y internal sealed class BinaryOr(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -313,53 +314,53 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.BinaryOr(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.BinaryOr(); } } // x == y internal sealed class Equal(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.Equal(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.Equal(); } } // x != y internal sealed class NotEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.NotEqual(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.NotEqual(); } } // x ~= y internal sealed class Equivalent(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.Equivalent(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.Equivalent(); } } // x ~! y internal sealed class NotEquivalent(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.NotEquivalent(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.NotEquivalent(); } } // x > y internal sealed class GreaterThan(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -376,23 +377,23 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.GreaterThan(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.GreaterThan(); } } // x >= y internal sealed class GreaterThanOrEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.GreaterThanOrEqual(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.GreaterThanOrEqual(); } - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -410,17 +411,16 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { } } - // x < y internal sealed class LessThan(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.LessThan(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.LessThan(); } - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -440,14 +440,14 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { // x <= y internal sealed class LessThanOrEqual(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.LessThanOrEqual(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.LessThanOrEqual(); } - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!LHS.TryAsConstant(out var lhs) || !RHS.TryAsConstant(out var rhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!LHS.TryAsConstant(compiler, out var lhs) || !RHS.TryAsConstant(compiler, out var rhs)) { constant = null; return false; } @@ -467,14 +467,14 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { // x || y internal sealed class Or(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (LHS.TryAsConstant(out var lhs)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (LHS.TryAsConstant(compiler, out var lhs)) { if (lhs.IsTruthy()) { constant = lhs; return true; } - if (RHS.TryAsConstant(out var rhs)) { + if (RHS.TryAsConstant(compiler, out var rhs)) { constant = rhs; return true; } @@ -484,25 +484,25 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return false; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - string endLabel = proc.NewLabelName(); + public override void EmitPushValue(ExpressionContext ctx) { + string endLabel = ctx.Proc.NewLabelName(); - LHS.EmitPushValue(dmObject, proc); - proc.BooleanOr(endLabel); - RHS.EmitPushValue(dmObject, proc); - proc.AddLabel(endLabel); + LHS.EmitPushValue(ctx); + ctx.Proc.BooleanOr(endLabel); + RHS.EmitPushValue(ctx); + ctx.Proc.AddLabel(endLabel); } } // x && y internal sealed class And(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (LHS.TryAsConstant(out var lhs) && !lhs.IsTruthy()) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (LHS.TryAsConstant(compiler, out var lhs) && !lhs.IsTruthy()) { constant = lhs; return true; } - if (RHS.TryAsConstant(out var rhs)) { + if (RHS.TryAsConstant(compiler, out var rhs)) { constant = rhs; return true; } @@ -511,43 +511,47 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return false; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - string endLabel = proc.NewLabelName(); + public override void EmitPushValue(ExpressionContext ctx) { + string endLabel = ctx.Proc.NewLabelName(); - LHS.EmitPushValue(dmObject, proc); - proc.BooleanAnd(endLabel); - RHS.EmitPushValue(dmObject, proc); - proc.AddLabel(endLabel); + LHS.EmitPushValue(ctx); + ctx.Proc.BooleanAnd(endLabel); + RHS.EmitPushValue(ctx); + ctx.Proc.AddLabel(endLabel); } } // x in y internal sealed class In(Location location, DMExpression expr, DMExpression container) : BinaryOp(location, expr, container) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - LHS.EmitPushValue(dmObject, proc); - RHS.EmitPushValue(dmObject, proc); - proc.IsInList(); + public override void EmitPushValue(ExpressionContext ctx) { + LHS.EmitPushValue(ctx); + RHS.EmitPushValue(ctx); + ctx.Proc.IsInList(); } } + #endregion #region Compound Assignment + internal abstract class AssignmentBinaryOp(Location location, DMExpression lhs, DMExpression rhs) : BinaryOp(location, lhs, rhs) { /// /// Generic interface for emitting the assignment operation. Has its conditionality and reference generation already handled. /// /// You should always make use of the reference argument, unless you totally override AssignmentBinaryOp's EmitPushValue method. - /// A reference to the LHS emitted via - protected abstract void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel); + /// + /// A reference to the LHS emitted via + /// + protected abstract void EmitOp(ExpressionContext ctx, DMReference reference, string endLabel); - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - string endLabel = proc.NewLabelName(); + public override void EmitPushValue(ExpressionContext ctx) { + string endLabel = ctx.Proc.NewLabelName(); - DMReference reference = LHS.EmitReference(dmObject, proc, endLabel); - EmitOp(dmObject, proc, reference, endLabel); + DMReference reference = LHS.EmitReference(ctx, endLabel); + EmitOp(ctx, reference, endLabel); - proc.AddLabel(endLabel); + ctx.Proc.AddLabel(endLabel); } } @@ -555,15 +559,15 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class Assignment(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { public override DreamPath? Path => LHS.Path; - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.Assign(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.Assign(reference); - if (!LHS.ValType.MatchesType(RHS.ValType) && !LHS.ValType.IsUnimplemented) { - if (DMCompiler.Settings.SkipAnythingTypecheck && RHS.ValType.IsAnything) + if (!LHS.ValType.MatchesType(ctx.Compiler, RHS.ValType) && !LHS.ValType.IsUnimplemented) { + if (ctx.Compiler.Settings.SkipAnythingTypecheck && RHS.ValType.IsAnything) return; - DMCompiler.Emit(WarningCode.InvalidVarType, Location, + ctx.Compiler.Emit(WarningCode.InvalidVarType, Location, $"Invalid var type {RHS.ValType}, expected {LHS.ValType}"); } } @@ -573,115 +577,130 @@ protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference refer internal sealed class AssignmentInto(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { public override DreamPath? Path => LHS.Path; - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.AssignInto(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.AssignInto(reference); } } // x += y internal sealed class Append(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.Append(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.Append(reference); } } // x |= y internal sealed class Combine(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.Combine(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.Combine(reference); } } // x -= y internal sealed class Remove(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.Remove(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.Remove(reference); } } // x &= y internal sealed class Mask(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.Mask(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.Mask(reference); } } // x &&= y internal sealed class LogicalAndAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - proc.JumpIfFalseReference(reference, endLabel); - RHS.EmitPushValue(dmObject, proc); - proc.Assign(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + ctx.Proc.JumpIfFalseReference(reference, endLabel); + RHS.EmitPushValue(ctx); + ctx.Proc.Assign(reference); } } // x ||= y internal sealed class LogicalOrAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - proc.JumpIfTrueReference(reference, endLabel); - RHS.EmitPushValue(dmObject, proc); - proc.Assign(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + ctx.Proc.JumpIfTrueReference(reference, endLabel); + RHS.EmitPushValue(ctx); + ctx.Proc.Assign(reference); } } // x *= y internal sealed class MultiplyAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.MultiplyReference(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.MultiplyReference(reference); } } // x /= y internal sealed class DivideAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.DivideReference(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.DivideReference(reference); } } // x <<= y internal sealed class LeftShiftAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.BitShiftLeftReference(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.BitShiftLeftReference(reference); } } // x >>= y internal sealed class RightShiftAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.BitShiftRightReference(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.BitShiftRightReference(reference); } } // x ^= y internal sealed class XorAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.BinaryXorReference(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.BinaryXorReference(reference); } } // x %= y internal sealed class ModulusAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.ModulusReference(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.ModulusReference(reference); } } // x %%= y internal sealed class ModulusModulusAssign(Location location, DMExpression lhs, DMExpression rhs) : AssignmentBinaryOp(location, lhs, rhs) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { - RHS.EmitPushValue(dmObject, proc); - proc.ModulusModulusReference(reference); + protected override void EmitOp(ExpressionContext ctx, DMReference reference, + string endLabel) { + RHS.EmitPushValue(ctx); + ctx.Proc.ModulusModulusReference(reference); } } + #endregion diff --git a/DMCompiler/DM/Expressions/Builtins.cs b/DMCompiler/DM/Expressions/Builtins.cs index f09ab45566..4b7ec230b6 100644 --- a/DMCompiler/DM/Expressions/Builtins.cs +++ b/DMCompiler/DM/Expressions/Builtins.cs @@ -1,7 +1,6 @@ using DMCompiler.Bytecode; using System.Diagnostics.CodeAnalysis; using DMCompiler.Compiler; -using DMCompiler.Compiler.DM.AST; using DMCompiler.Json; namespace DMCompiler.DM.Expressions; @@ -11,11 +10,26 @@ namespace DMCompiler.DM.Expressions; ///
/// Emit an error code before creating! internal sealed class BadExpression(Location location) : DMExpression(location) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { // It's normal to have this expression exist when there are errors in the code // But in the runtime we say it's a compiler bug because the compiler should never have output it - proc.PushString("Encountered a bad expression (compiler bug!)"); - proc.Throw(); + ctx.Proc.PushString("Encountered a bad expression (compiler bug!)"); + ctx.Proc.Throw(); + } +} + +internal sealed class UnknownReference(Location location, string message) : DMExpression(location) { + public string Message => message; + + public override void EmitPushValue(ExpressionContext ctx) { + // It's normal to have this expression exist when there's out-of-order definitions in the code + // But in the runtime we say it's a compiler bug because the compiler should never have output it + ctx.Proc.PushString("Encountered an unknown reference expression (compiler bug!)"); + ctx.Proc.Throw(); + } + + public void EmitCompilerError(DMCompiler compiler) { + compiler.Emit(WarningCode.ItemDoesntExist, Location, message); } } @@ -23,76 +37,68 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class StringFormat(Location location, string value, DMExpression[] expressions) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Text; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { foreach (DMExpression expression in expressions) { - expression.EmitPushValue(dmObject, proc); + expression.EmitPushValue(ctx); } - proc.FormatString(value); + ctx.Proc.FormatString(value); } } // arglist(...) internal sealed class Arglist(Location location, DMExpression expr) : DMExpression(location) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - DMCompiler.Emit(WarningCode.BadExpression, Location, "invalid use of arglist"); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "invalid use of arglist"); } - public void EmitPushArglist(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); + public void EmitPushArglist(ExpressionContext ctx) { + expr.EmitPushValue(ctx); } } // new x (...) -internal sealed class New(Location location, DMExpression expr, ArgumentList arguments) : DMExpression(location) { +internal sealed class New(DMCompiler compiler, Location location, DMExpression expr, ArgumentList arguments) : DMExpression(location) { + public override DreamPath? Path => expr.Path; public override bool PathIsFuzzy => Path == null; - public override DMComplexValueType ValType => !expr.ValType.IsAnything ? expr.ValType : (Path?.GetAtomType() ?? DMValueType.Anything); + public override DMComplexValueType ValType => !expr.ValType.IsAnything ? expr.ValType : (Path?.GetAtomType(compiler) ?? DMValueType.Anything); - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - var argumentInfo = arguments.EmitArguments(dmObject, proc, null); + public override void EmitPushValue(ExpressionContext ctx) { + var argumentInfo = arguments.EmitArguments(ctx, null); - expr.EmitPushValue(dmObject, proc); - proc.CreateObject(argumentInfo.Type, argumentInfo.StackSize); + expr.EmitPushValue(ctx); + ctx.Proc.CreateObject(argumentInfo.Type, argumentInfo.StackSize); } } // new /x/y/z (...) -internal sealed class NewPath(Location location, ConstantPath targetPath, ArgumentList arguments) : DMExpression(location) { - public override DreamPath? Path => targetPath.Value; - public override DMComplexValueType ValType => targetPath.Value.GetAtomType(); - - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - if (!targetPath.TryResolvePath(out var pathInfo)) { - proc.PushNull(); - return; - } +internal sealed class NewPath(DMCompiler compiler, Location location, IConstantPath create, ArgumentList arguments) : DMExpression(location) { + public override DreamPath? Path => (create is ConstantTypeReference typeReference) ? typeReference.Path : null; + public override DMComplexValueType ValType => Path?.GetAtomType(compiler) ?? DMValueType.Anything; + public override void EmitPushValue(ExpressionContext ctx) { DMCallArgumentsType argumentsType; int stackSize; - switch (pathInfo.Value.Type) { - case ConstantPath.PathType.TypeReference: - var newProc = DMObjectTree.GetNewProc(pathInfo.Value.Id); + switch (create) { + case ConstantTypeReference typeReference: + // ctx: This might give us null depending on how definition order goes + var newProc = ctx.ObjectTree.GetNewProc(typeReference.Value.Id); - (argumentsType, stackSize) = arguments.EmitArguments(dmObject, proc, newProc); - proc.PushType(pathInfo.Value.Id); + (argumentsType, stackSize) = arguments.EmitArguments(ctx, newProc); + ctx.Proc.PushType(typeReference.Value.Id); break; - case ConstantPath.PathType.ProcReference: // "new /proc/new_verb(Destination)" is a thing - (argumentsType, stackSize) = arguments.EmitArguments(dmObject, proc, DMObjectTree.AllProcs[pathInfo.Value.Id]); - proc.PushProc(pathInfo.Value.Id); + case ConstantProcReference procReference: // "new /proc/new_verb(Destination)" is a thing + (argumentsType, stackSize) = arguments.EmitArguments(ctx, ctx.ObjectTree.AllProcs[procReference.Value.Id]); + ctx.Proc.PushProc(procReference.Value.Id); break; - case ConstantPath.PathType.ProcStub: - case ConstantPath.PathType.VerbStub: - DMCompiler.Emit(WarningCode.BadExpression, Location, "Cannot use \"new\" with a proc stub"); - proc.PushNull(); - return; default: - DMCompiler.Emit(WarningCode.BadExpression, Location, "Invalid path info type"); - proc.PushNull(); + ctx.Compiler.Emit(WarningCode.BadExpression, Location, $"Cannot instantiate {create}"); + ctx.Proc.PushNull(); return; } - proc.CreateObject(argumentsType, stackSize); + ctx.Proc.CreateObject(argumentsType, stackSize); } } @@ -100,29 +106,28 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class LocateInferred(Location location, DreamPath path, DMExpression? container) : DMExpression(location) { public override DMComplexValueType ValType => path; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - if (!DMObjectTree.TryGetTypeId(path, out var typeId)) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {path} does not exist"); + public override void EmitPushValue(ExpressionContext ctx) { + if (!ctx.ObjectTree.TryGetTypeId(path, out var typeId)) { + ctx.Compiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {path} does not exist"); return; } - proc.PushType(typeId); + ctx.Proc.PushType(typeId); if (container != null) { - container.EmitPushValue(dmObject, proc); + container.EmitPushValue(ctx); } else { - if (DMCompiler.Settings.NoStandard) { - DMCompiler.Emit(WarningCode.BadExpression, Location, "Implicit locate() container is not available with --no-standard"); - proc.Error(); + if (ctx.Compiler.Settings.NoStandard) { + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "Implicit locate() container is not available with --no-standard"); + ctx.Proc.Error(); return; } - DMReference world = DMReference.CreateGlobal(dmObject.GetGlobalVariableId("world").Value); - proc.PushReferenceValue(world); + ctx.Proc.PushReferenceValue(DMReference.World); } - proc.Locate(); + ctx.Proc.Locate(); } } @@ -130,23 +135,22 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class Locate(Location location, DMExpression path, DMExpression? container) : DMExpression(location) { public override bool PathIsFuzzy => true; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - path.EmitPushValue(dmObject, proc); + public override void EmitPushValue(ExpressionContext ctx) { + path.EmitPushValue(ctx); if (container != null) { - container.EmitPushValue(dmObject, proc); + container.EmitPushValue(ctx); } else { - if (DMCompiler.Settings.NoStandard) { - DMCompiler.Emit(WarningCode.BadExpression, Location, "Implicit locate() container is not available with --no-standard"); - proc.Error(); + if (ctx.Compiler.Settings.NoStandard) { + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "Implicit locate() container is not available with --no-standard"); + ctx.Proc.Error(); return; } - DMReference world = DMReference.CreateGlobal(dmObject.GetGlobalVariableId("world").Value); - proc.PushReferenceValue(world); + ctx.Proc.PushReferenceValue(DMReference.World); } - proc.Locate(); + ctx.Proc.Locate(); } } @@ -154,22 +158,22 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class LocateCoordinates(Location location, DMExpression x, DMExpression y, DMExpression z) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Turf; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - x.EmitPushValue(dmObject, proc); - y.EmitPushValue(dmObject, proc); - z.EmitPushValue(dmObject, proc); - proc.LocateCoordinates(); + public override void EmitPushValue(ExpressionContext ctx) { + x.EmitPushValue(ctx); + y.EmitPushValue(ctx); + z.EmitPushValue(ctx); + ctx.Proc.LocateCoordinates(); } } // gradient(Gradient, index) // gradient(Item1, Item2, ..., index) internal sealed class Gradient(Location location, ArgumentList arguments) : DMExpression(location) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - DMObjectTree.TryGetGlobalProc("gradient", out var dmProc); - var argInfo = arguments.EmitArguments(dmObject, proc, dmProc); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.ObjectTree.TryGetGlobalProc("gradient", out var dmProc); + var argInfo = arguments.EmitArguments(ctx, dmProc); - proc.Gradient(argInfo.Type, argInfo.StackSize); + ctx.Proc.Gradient(argInfo.Type, argInfo.StackSize); } } @@ -178,11 +182,11 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { /// rgb(x, y, z, space) /// rgb(x, y, z, a, space) internal sealed class Rgb(Location location, ArgumentList arguments) : DMExpression(location) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - DMObjectTree.TryGetGlobalProc("rgb", out var dmProc); - var argInfo = arguments.EmitArguments(dmObject, proc, dmProc); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.ObjectTree.TryGetGlobalProc("rgb", out var dmProc); + var argInfo = arguments.EmitArguments(ctx, dmProc); - proc.Rgb(argInfo.Type, argInfo.StackSize); + ctx.Proc.Rgb(argInfo.Type, argInfo.StackSize); } } @@ -195,7 +199,7 @@ public struct PickValue(DMExpression? weight, DMExpression value) { public readonly DMExpression Value = value; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { bool weighted = false; foreach (PickValue pickValue in values) { if (pickValue.Weight != null) { @@ -206,31 +210,31 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { if (weighted) { if (values.Length == 1) { - DMCompiler.ForcedWarning(Location, "Weighted pick() with one argument"); + ctx.Compiler.ForcedWarning(Location, "Weighted pick() with one argument"); } - DMCompiler.Emit(WarningCode.PickWeightedSyntax, Location, "Use of weighted pick() syntax"); + ctx.Compiler.Emit(WarningCode.PickWeightedSyntax, Location, "Use of weighted pick() syntax"); foreach (PickValue pickValue in values) { - DMExpression weight = pickValue.Weight ?? DMExpression.Create(dmObject, proc, new DMASTConstantInteger(Location, 100)); //Default of 100 + DMExpression weight = pickValue.Weight ?? new Number(Location.Internal, 100); //Default of 100 - weight.EmitPushValue(dmObject, proc); - pickValue.Value.EmitPushValue(dmObject, proc); + weight.EmitPushValue(ctx); + pickValue.Value.EmitPushValue(ctx); } - proc.PickWeighted(values.Length); + ctx.Proc.PickWeighted(values.Length); } else { foreach (PickValue pickValue in values) { if (pickValue.Value is Arglist args) { // This will just push a list which pick() accepts // Really hacky and won't verify that the value is actually a list - args.EmitPushArglist(dmObject, proc); + args.EmitPushArglist(ctx); } else { - pickValue.Value.EmitPushValue(dmObject, proc); + pickValue.Value.EmitPushValue(ctx); } } - proc.PickUnweighted(values.Length); + ctx.Proc.PickUnweighted(values.Length); } } } @@ -240,15 +244,15 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class AddText(Location location, DMExpression[] paras) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Text; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { //We don't have to do any checking of our parameters since that was already done by VisitAddText(), hopefully. :) //Push addtext()'s arguments foreach (DMExpression parameter in paras) { - parameter.EmitPushValue(dmObject, proc); + parameter.EmitPushValue(ctx); } - proc.MassConcatenation(paras.Length); + ctx.Proc.MassConcatenation(paras.Length); } } @@ -258,9 +262,9 @@ internal sealed class Prob(Location location, DMExpression p) : DMExpression(loc public override DMComplexValueType ValType => DMValueType.Num; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - P.EmitPushValue(dmObject, proc); - proc.Prob(); + public override void EmitPushValue(ExpressionContext ctx) { + P.EmitPushValue(ctx); + ctx.Proc.Prob(); } } @@ -268,20 +272,20 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class IsSaved(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { switch (expr) { case Dereference deref: - deref.EmitPushIsSaved(dmObject, proc); + deref.EmitPushIsSaved(ctx); return; case Field field: - field.EmitPushIsSaved(proc); + field.EmitPushIsSaved(ctx.Proc); return; case Local: - proc.PushFloat(0); + ctx.Proc.PushFloat(0); return; default: - DMCompiler.Emit(WarningCode.BadArgument, expr.Location, $"can't get saved value of {expr}"); - proc.Error(); + ctx.Compiler.Emit(WarningCode.BadArgument, expr.Location, $"can't get saved value of {expr}"); + ctx.Proc.Error(); return; } } @@ -291,10 +295,10 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class IsType(Location location, DMExpression expr, DMExpression path) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - path.EmitPushValue(dmObject, proc); - proc.IsType(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + path.EmitPushValue(ctx); + ctx.Proc.IsType(); } } @@ -302,16 +306,16 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class IsTypeInferred(Location location, DMExpression expr, DreamPath path) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - if (!DMObjectTree.TryGetTypeId(path, out var typeId)) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {path} does not exist"); + public override void EmitPushValue(ExpressionContext ctx) { + if (!ctx.ObjectTree.TryGetTypeId(path, out var typeId)) { + ctx.Compiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {path} does not exist"); return; } - expr.EmitPushValue(dmObject, proc); - proc.PushType(typeId); - proc.IsType(); + expr.EmitPushValue(ctx); + ctx.Proc.PushType(typeId); + ctx.Proc.IsType(); } } @@ -320,9 +324,9 @@ internal sealed class IsNull(Location location, DMExpression value) : DMExpressi public override bool PathIsFuzzy => true; public override DMComplexValueType ValType => DMValueType.Num; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - value.EmitPushValue(dmObject, proc); - proc.IsNull(); + public override void EmitPushValue(ExpressionContext ctx) { + value.EmitPushValue(ctx); + ctx.Proc.IsNull(); } } @@ -331,9 +335,9 @@ internal sealed class Length(Location location, DMExpression value) : DMExpressi public override bool PathIsFuzzy => true; public override DMComplexValueType ValType => DMValueType.Num; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - value.EmitPushValue(dmObject, proc); - proc.Length(); + public override void EmitPushValue(ExpressionContext ctx) { + value.EmitPushValue(ctx); + ctx.Proc.Length(); } } @@ -341,10 +345,10 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class GetStep(Location location, DMExpression refValue, DMExpression dir) : DMExpression(location) { public override bool PathIsFuzzy => true; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - refValue.EmitPushValue(dmObject, proc); - dir.EmitPushValue(dmObject, proc); - proc.GetStep(); + public override void EmitPushValue(ExpressionContext ctx) { + refValue.EmitPushValue(ctx); + dir.EmitPushValue(ctx); + ctx.Proc.GetStep(); } } @@ -352,10 +356,10 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class GetDir(Location location, DMExpression loc1, DMExpression loc2) : DMExpression(location) { public override bool PathIsFuzzy => true; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - loc1.EmitPushValue(dmObject, proc); - loc2.EmitPushValue(dmObject, proc); - proc.GetDir(); + public override void EmitPushValue(ExpressionContext ctx) { + loc1.EmitPushValue(ctx); + loc2.EmitPushValue(ctx); + ctx.Proc.GetDir(); } } @@ -379,38 +383,38 @@ public List(Location location, (DMExpression? Key, DMExpression Value)[] values) } } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { foreach (var value in _values) { if (_isAssociative) { if (value.Key == null) { - proc.PushNull(); + ctx.Proc.PushNull(); } else { - value.Key.EmitPushValue(dmObject, proc); + value.Key.EmitPushValue(ctx); } } - value.Value.EmitPushValue(dmObject, proc); + value.Value.EmitPushValue(ctx); } if (_isAssociative) { - proc.CreateAssociativeList(_values.Length); + ctx.Proc.CreateAssociativeList(_values.Length); } else { - proc.CreateList(_values.Length); + ctx.Proc.CreateList(_values.Length); } } - public override bool TryAsJsonRepresentation(out object? json) { + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { List values = new(); foreach (var value in _values) { - if (!value.Value.TryAsJsonRepresentation(out var jsonValue)) { + if (!value.Value.TryAsJsonRepresentation(compiler, out var jsonValue)) { json = null; return false; } if (value.Key != null) { // Null key is not supported here - if (!value.Key.TryAsJsonRepresentation(out var jsonKey) || jsonKey == null) { + if (!value.Key.TryAsJsonRepresentation(compiler, out var jsonKey) || jsonKey == null) { json = null; return false; } @@ -435,20 +439,13 @@ public override bool TryAsJsonRepresentation(out object? json) { // Value of var/list/L[1][2][3] internal sealed class DimensionalList(Location location, DMExpression[] sizes) : DMExpression(location) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - // This basically emits new /list(1, 2, 3) - - if (!DMObjectTree.TryGetTypeId(DreamPath.List, out var listTypeId)) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, "Could not get type ID of /list"); - return; - } - + public override void EmitPushValue(ExpressionContext ctx) { foreach (var size in sizes) { - size.EmitPushValue(dmObject, proc); + size.EmitPushValue(ctx); } - proc.PushType(listTypeId); - proc.CreateObject(DMCallArgumentsType.FromStack, sizes.Length); + // Should be equivalent to new /list(1, 2, 3) + ctx.Proc.CreateMultidimensionalList(sizes.Length); } } @@ -456,19 +453,19 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class NewList(Location location, DMExpression[] parameters) : DMExpression(location) { public override DMComplexValueType ValType => DreamPath.List; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { foreach (DMExpression parameter in parameters) { - parameter.EmitPushValue(dmObject, proc); - proc.CreateObject(DMCallArgumentsType.None, 0); + parameter.EmitPushValue(ctx); + ctx.Proc.CreateObject(DMCallArgumentsType.None, 0); } - proc.CreateList(parameters.Length); + ctx.Proc.CreateList(parameters.Length); } - public override bool TryAsJsonRepresentation(out object? json) { + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { json = null; - DMCompiler.UnimplementedWarning(Location, "DMM overrides for newlist() are not implemented"); - return true; //TODO + compiler.UnimplementedWarning(Location, "DMM overrides for newlist() are not implemented"); + return true; //ctx } } @@ -477,24 +474,24 @@ internal sealed class Input(Location location, DMExpression[] arguments, DMValue : DMExpression(location) { public override DMComplexValueType ValType => types; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { // Push input's four arguments, pushing null for the missing ones for (int i = 3; i >= 0; i--) { if (i < arguments.Length) { - arguments[i].EmitPushValue(dmObject, proc); + arguments[i].EmitPushValue(ctx); } else { - proc.PushNull(); + ctx.Proc.PushNull(); } } // The list of values to be selected from (or null for none) if (list != null) { - list.EmitPushValue(dmObject, proc); + list.EmitPushValue(ctx); } else { - proc.PushNull(); + ctx.Proc.PushNull(); } - proc.Prompt(types); + ctx.Proc.Prompt(types); } } @@ -502,14 +499,14 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal class Initial(Location location, DMExpression expr) : DMExpression(location) { protected DMExpression Expression { get; } = expr; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { if (Expression is LValue lValue) { - lValue.EmitPushInitial(dmObject, proc); + lValue.EmitPushInitial(ctx); return; } - DMCompiler.Emit(WarningCode.BadArgument, Expression.Location, $"can't get initial value of {Expression}"); - proc.Error(); + ctx.Compiler.Emit(WarningCode.BadArgument, Expression.Location, $"can't get initial value of {Expression}"); + ctx.Proc.Error(); } } @@ -530,12 +527,12 @@ public CallStatement(Location location, DMExpression a, DMExpression b, Argument _procArgs = procArgs; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - var argumentInfo = _procArgs.EmitArguments(dmObject, proc, null); + public override void EmitPushValue(ExpressionContext ctx) { + var argumentInfo = _procArgs.EmitArguments(ctx, null); - _b?.EmitPushValue(dmObject, proc); - _a.EmitPushValue(dmObject, proc); - proc.CallStatement(argumentInfo.Type, argumentInfo.StackSize); + _b?.EmitPushValue(ctx); + _a.EmitPushValue(ctx); + ctx.Proc.CallStatement(argumentInfo.Type, argumentInfo.StackSize); } } @@ -545,21 +542,21 @@ internal sealed class ProcOwnerType(Location location, DMObject owner) : DMExpre public override DMComplexValueType ValType => (OwnerPath != null) ? OwnerPath.Value : DMValueType.Null; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { + public override void EmitPushValue(ExpressionContext ctx) { // BYOND returns null if this is called in a global proc - if (dmObject.Path == DreamPath.Root) { - proc.PushNull(); + if (ctx.Type.Path == DreamPath.Root) { + ctx.Proc.PushNull(); } else { - proc.PushType(dmObject.Id); + ctx.Proc.PushType(ctx.Type.Id); } } - public override string? GetNameof(DMObject dmObject) { - if (dmObject.Path.LastElement != null) { - return dmObject.Path.LastElement; + public override string? GetNameof(ExpressionContext ctx) { + if (ctx.Type.Path.LastElement != null) { + return ctx.Type.Path.LastElement; } - DMCompiler.Emit(WarningCode.BadArgument, Location, "Attempt to get nameof(__TYPE__) in global proc"); + ctx.Compiler.Emit(WarningCode.BadArgument, Location, "Attempt to get nameof(__TYPE__) in global proc"); return null; } } @@ -567,15 +564,15 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class Sin(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var x}) { x = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, + compiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, "Invalid value treated as 0, sin(0) will always be 0"); } @@ -583,24 +580,24 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - proc.Sin(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + ctx.Proc.Sin(); } } internal sealed class Cos(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var x}) { x = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, + compiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, "Invalid value treated as 0, cos(0) will always be 1"); } @@ -608,24 +605,24 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - proc.Cos(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + ctx.Proc.Cos(); } } internal sealed class Tan(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var x}) { x = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, + compiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, "Invalid value treated as 0, tan(0) will always be 0"); } @@ -633,29 +630,29 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - proc.Tan(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + ctx.Proc.Tan(); } } internal sealed class ArcSin(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var x}) { x = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, + compiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, "Invalid value treated as 0, arcsin(0) will always be 0"); } if (x is < -1 or > 1) { - DMCompiler.Emit(WarningCode.BadArgument, expr.Location, $"Invalid value {x}, must be >= -1 and <= 1"); + compiler.Emit(WarningCode.BadArgument, expr.Location, $"Invalid value {x}, must be >= -1 and <= 1"); x = 0; } @@ -663,29 +660,29 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - proc.ArcSin(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + ctx.Proc.ArcSin(); } } internal sealed class ArcCos(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var x}) { x = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, + compiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, "Invalid value treated as 0, arccos(0) will always be 1"); } if (x is < -1 or > 1) { - DMCompiler.Emit(WarningCode.BadArgument, expr.Location, $"Invalid value {x}, must be >= -1 and <= 1"); + compiler.Emit(WarningCode.BadArgument, expr.Location, $"Invalid value {x}, must be >= -1 and <= 1"); x = 0; } @@ -693,24 +690,24 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - proc.ArcCos(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + ctx.Proc.ArcCos(); } } internal sealed class ArcTan(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var a}) { a = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, + compiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, "Invalid value treated as 0, arctan(0) will always be 0"); } @@ -718,59 +715,59 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - proc.ArcTan(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + ctx.Proc.ArcTan(); } } internal sealed class ArcTan2(Location location, DMExpression xExpr, DMExpression yExpr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!xExpr.TryAsConstant(out var xConst) || !yExpr.TryAsConstant(out var yConst)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!xExpr.TryAsConstant(compiler, out var xConst) || !yExpr.TryAsConstant(compiler, out var yConst)) { constant = null; return false; } if (xConst is not Number {Value: var x}) { x = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, xExpr.Location, "Invalid x value treated as 0"); + compiler.Emit(WarningCode.FallbackBuiltinArgument, xExpr.Location, "Invalid x value treated as 0"); } if (yConst is not Number {Value: var y}) { y = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, xExpr.Location, "Invalid y value treated as 0"); + compiler.Emit(WarningCode.FallbackBuiltinArgument, xExpr.Location, "Invalid y value treated as 0"); } constant = new Number(Location, SharedOperations.ArcTan(x, y)); return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - xExpr.EmitPushValue(dmObject, proc); - yExpr.EmitPushValue(dmObject, proc); - proc.ArcTan2(); + public override void EmitPushValue(ExpressionContext ctx) { + xExpr.EmitPushValue(ctx); + yExpr.EmitPushValue(ctx); + ctx.Proc.ArcTan2(); } } internal sealed class Sqrt(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var a}) { a = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, + compiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, "Invalid value treated as 0, sqrt(0) will always be 0"); } if (a < 0) { - DMCompiler.Emit(WarningCode.BadArgument, expr.Location, + compiler.Emit(WarningCode.BadArgument, expr.Location, $"Cannot get the square root of a negative number ({a})"); } @@ -778,9 +775,9 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - proc.Sqrt(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + ctx.Proc.Sqrt(); } } @@ -788,15 +785,15 @@ internal sealed class Log(Location location, DMExpression expr, DMExpression? ba : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var value} || value <= 0) { value = 1; - DMCompiler.Emit(WarningCode.BadArgument, expr.Location, + compiler.Emit(WarningCode.BadArgument, expr.Location, "Invalid value, must be a number greater than 0"); } @@ -805,14 +802,14 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - if (!baseExpr.TryAsConstant(out var baseConstant)) { + if (!baseExpr.TryAsConstant(compiler, out var baseConstant)) { constant = null; return false; } if (baseConstant is not Number {Value: var baseValue} || baseValue <= 0) { baseValue = 10; - DMCompiler.Emit(WarningCode.BadArgument, baseExpr.Location, + compiler.Emit(WarningCode.BadArgument, baseExpr.Location, "Invalid base, must be a number greater than 0"); } @@ -820,13 +817,13 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); if (baseExpr == null) { - proc.LogE(); + ctx.Proc.LogE(); } else { - baseExpr.EmitPushValue(dmObject, proc); - proc.Log(); + baseExpr.EmitPushValue(ctx); + ctx.Proc.Log(); } } } @@ -834,15 +831,15 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { internal sealed class Abs(Location location, DMExpression expr) : DMExpression(location) { public override DMComplexValueType ValType => DMValueType.Num; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!expr.TryAsConstant(out constant)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!expr.TryAsConstant(compiler, out constant)) { constant = null; return false; } if (constant is not Number {Value: var a}) { a = 0; - DMCompiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, + compiler.Emit(WarningCode.FallbackBuiltinArgument, expr.Location, "Invalid value treated as 0, abs(0) will always be 0"); } @@ -850,8 +847,8 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - expr.EmitPushValue(dmObject, proc); - proc.Abs(); + public override void EmitPushValue(ExpressionContext ctx) { + expr.EmitPushValue(ctx); + ctx.Proc.Abs(); } } diff --git a/DMCompiler/DM/Expressions/Constant.cs b/DMCompiler/DM/Expressions/Constant.cs index 6b4cb135d9..d9f6b256a9 100644 --- a/DMCompiler/DM/Expressions/Constant.cs +++ b/DMCompiler/DM/Expressions/Constant.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using DMCompiler.Compiler; @@ -7,7 +6,7 @@ namespace DMCompiler.DM.Expressions; internal abstract class Constant(Location location) : DMExpression(location) { - public sealed override bool TryAsConstant(out Constant constant) { + public sealed override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { constant = this; return true; } @@ -19,13 +18,13 @@ public sealed override bool TryAsConstant(out Constant constant) { internal sealed class Null(Location location) : Constant(location) { public override DMComplexValueType ValType => DMValueType.Null; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - proc.PushNull(); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Proc.PushNull(); } public override bool IsTruthy() => false; - public override bool TryAsJsonRepresentation(out object? json) { + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { json = null; return true; } @@ -45,13 +44,13 @@ public Number(Location location, float value) : base(location) { Value = value; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - proc.PushFloat(Value); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Proc.PushFloat(Value); } public override bool IsTruthy() => Value != 0; - public override bool TryAsJsonRepresentation(out object? json) { + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { // Positive/Negative infinity cannot be represented in JSON and need a special value if (float.IsPositiveInfinity(Value)) { json = new Dictionary() { @@ -75,13 +74,13 @@ internal sealed class String(Location location, string value) : Constant(locatio public override DMComplexValueType ValType => DMValueType.Text; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - proc.PushString(Value); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Proc.PushString(Value); } public override bool IsTruthy() => Value.Length != 0; - public override bool TryAsJsonRepresentation(out object? json) { + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { json = Value; return true; } @@ -102,12 +101,12 @@ internal sealed class Resource : Constant { private readonly string _filePath; private bool _isAmbiguous; - public Resource(Location location, string filePath) : base(location) { + public Resource(DMCompiler compiler, Location location, string filePath) : base(location) { // Treat backslashes as forward slashes on Linux // Also remove "." and ".." from the directory path filePath = System.IO.Path.GetRelativePath(".", filePath.Replace('\\', '/')); - var outputDir = System.IO.Path.GetDirectoryName(DMCompiler.Settings.Files?[0]) ?? "/"; + var outputDir = System.IO.Path.GetDirectoryName(compiler.Settings.Files?[0]) ?? "/"; if (string.IsNullOrEmpty(outputDir)) outputDir = "./"; @@ -117,7 +116,7 @@ public Resource(Location location, string filePath) : base(location) { var fileDir = System.IO.Path.GetDirectoryName(filePath) ?? string.Empty; // Search every defined FILE_DIR - foreach (string resourceDir in DMCompiler.ResourceDirectories) { + foreach (string resourceDir in compiler.ResourceDirectories) { var directory = FindDirectory(resourceDir, fileDir); if (directory != null) { @@ -142,11 +141,11 @@ public Resource(Location location, string filePath) : base(location) { _filePath = System.IO.Path.GetRelativePath(outputDir, finalFilePath); if (_isAmbiguous) { - DMCompiler.Emit(WarningCode.AmbiguousResourcePath, Location, + compiler.Emit(WarningCode.AmbiguousResourcePath, Location, $"Resource {filePath} has multiple case-insensitive matches, using {_filePath}"); } } else { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Cannot find file '{filePath}'"); + compiler.Emit(WarningCode.ItemDoesntExist, Location, $"Cannot find file '{filePath}'"); _filePath = filePath; } @@ -154,16 +153,16 @@ public Resource(Location location, string filePath) : base(location) { // Compile-time resources always use forward slashes _filePath = _filePath.Replace('\\', '/'); - DMObjectTree.Resources.Add(_filePath); + compiler.DMObjectTree.Resources.Add(_filePath); } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - proc.PushResource(_filePath); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Proc.PushResource(_filePath); } public override bool IsTruthy() => true; - public override bool TryAsJsonRepresentation(out object? json) { + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { json = new Dictionary() { { "type", JsonVariableType.Resource }, { "resourcePath", _filePath } @@ -219,179 +218,84 @@ public override bool TryAsJsonRepresentation(out object? json) { } } -// /a/b/c -// no, this can't be called "Path" because of CS0542 -internal sealed class ConstantPath(Location location, DMObject dmObject, DreamPath value) : Constant(location) { - public DreamPath Value { get; } = value; - - /// - /// The DMObject this expression resides in. Used for path searches. - /// - private readonly DMObject _dmObject = dmObject; - - public override DreamPath? Path => Value; - public override DMComplexValueType ValType => Value; +internal interface IConstantPath { + public DreamPath? Path { get; } +} - public enum PathType { - TypeReference, - ProcReference, - ProcStub, - VerbStub - } +/// +/// A reference to a type +/// /a/b/c +/// +internal class ConstantTypeReference(Location location, DMObject dmObject) : Constant(location), IConstantPath { + public DMObject Value { get; } = dmObject; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - if (!TryResolvePath(out var pathInfo)) { - proc.PushNull(); - return; - } + public override DreamPath? Path => Value.Path; + public override DMComplexValueType ValType => Value.Path; - switch (pathInfo.Value.Type) { - case PathType.TypeReference: - proc.PushType(pathInfo.Value.Id); - break; - case PathType.ProcReference: - proc.PushProc(pathInfo.Value.Id); - break; - case PathType.ProcStub: - case PathType.VerbStub: - var type = DMObjectTree.AllObjects[pathInfo.Value.Id].Path.PathString; - - // /datum/proc and /datum/verb just compile down to strings lmao - proc.PushString($"{type}/{(pathInfo.Value.Type == PathType.ProcStub ? "proc" : "verb")}"); - break; - default: - DMCompiler.ForcedError(Location, $"Invalid PathType {pathInfo.Value.Type}"); - break; - } + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Proc.PushType(Value.Id); } - public override string? GetNameof(DMObject dmObject) => Value.LastElement; + public override string? GetNameof(ExpressionContext ctx) => Value.Path.LastElement; public override bool IsTruthy() => true; - public override bool TryAsJsonRepresentation(out object? json) { - if (!TryResolvePath(out var pathInfo)) { - json = null; - return false; - } - - if (pathInfo.Value.Type is PathType.ProcStub or PathType.VerbStub) { - var type = DMObjectTree.AllObjects[pathInfo.Value.Id].Path.PathString; - - json = $"{type}/{(pathInfo.Value.Type == PathType.ProcStub ? "proc" : "verb")}"; - return true; - } - - JsonVariableType jsonType = pathInfo.Value.Type switch { - PathType.TypeReference => JsonVariableType.Type, - PathType.ProcReference => JsonVariableType.Proc, - _ => throw new UnreachableException() - }; - - json = new Dictionary() { - { "type", jsonType }, - { "value", pathInfo.Value.Id } + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { + json = new Dictionary { + { "type", JsonVariableType.Type }, + { "value", Value.Id } }; return true; } +} - public bool TryResolvePath([NotNullWhen(true)] out (PathType Type, int Id)? pathInfo) { - DreamPath path = Value; - - // An upward search with no left-hand side - if (Value.Type == DreamPath.PathType.UpwardSearch) { - DreamPath? foundPath = DMObjectTree.UpwardSearch(_dmObject.Path, path); - if (foundPath == null) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Could not find path {path}"); - - pathInfo = null; - return false; - } - - path = foundPath.Value; - } - - // /datum/proc and /datum/verb - if (Value.LastElement is "proc" or "verb") { - DreamPath typePath = Value.FromElements(0, -2); - if (!DMObjectTree.TryGetTypeId(typePath, out var ownerId)) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {typePath} does not exist"); - - pathInfo = null; - return false; - } - - pathInfo = Value.LastElement switch { - "proc" => (PathType.ProcStub, ownerId), - "verb" => (PathType.VerbStub, ownerId), - _ => throw new InvalidOperationException($"Last element of {Value} is not \"proc\" or \"verb\"") - }; - return true; - } +/// +/// A reference to a proc +/// /datum/proc/foo +/// +internal sealed class ConstantProcReference(Location location, DreamPath path, DMProc referencedProc) : Constant(location), IConstantPath { + public DMProc Value { get; } = referencedProc; - // /datum/proc/foo - int procIndex = path.FindElement("proc"); - if (procIndex == -1) procIndex = path.FindElement("verb"); - if (procIndex != -1) { - DreamPath withoutProcElement = path.RemoveElement(procIndex); - DreamPath ownerPath = withoutProcElement.FromElements(0, -2); - DMObject owner = DMObjectTree.GetDMObject(ownerPath, createIfNonexistent: false); - string procName = path.LastElement; - - int? procId; - if (owner == DMObjectTree.Root && DMObjectTree.TryGetGlobalProc(procName, out var globalProc)) { - procId = globalProc.Id; - } else { - var procs = owner.GetProcs(procName); - - procId = procs?[^1]; - } + public override DreamPath? Path => path; - if (procId == null) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, - $"Type {ownerPath} does not have a proc named {procName}"); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Proc.PushProc(Value.Id); + } - pathInfo = null; - return false; - } + public override string GetNameof(ExpressionContext ctx) => Value.Name; - pathInfo = (PathType.ProcReference, procId.Value); - return true; - } + public override bool IsTruthy() => true; - // Any other path - if (DMObjectTree.TryGetTypeId(Value, out var typeId)) { - pathInfo = (PathType.TypeReference, typeId); - return true; - } else { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {Value} does not exist"); + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { + json = new Dictionary { + { "type", JsonVariableType.Proc }, + { "value", Value.Id } + }; - pathInfo = null; - return false; - } + return true; } } -// TODO: Use this instead of ConstantPath for procs /// -/// A reference to a proc +/// A generic reference to all of a type's procs or verbs +/// /datum/proc /// -internal sealed class ConstantProcReference(Location location, DMProc referencedProc) : Constant(location) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - proc.PushProc(referencedProc.Id); - } +internal sealed class ConstantProcStub(Location location, DMObject onObject, bool isVerb) : Constant(location), IConstantPath { + private readonly string _str = + $"{(onObject.Path == DreamPath.Root ? string.Empty : onObject.Path.PathString)}/{(isVerb ? "verb" : "proc")}"; - public override string GetNameof(DMObject dmObject) => referencedProc.Name; + public override DreamPath? Path => onObject.Path.AddToPath(isVerb ? "verb" : "proc"); - public override bool IsTruthy() => true; + public override void EmitPushValue(ExpressionContext ctx) { + // /datum/proc and /datum/verb just compile down to strings lmao + ctx.Proc.PushString(_str); + } - public override bool TryAsJsonRepresentation(out object? json) { - json = new Dictionary { - { "type", JsonVariableType.Proc }, - { "value", referencedProc.Id } - }; + public override bool IsTruthy() => true; + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { + json = _str; return true; } } diff --git a/DMCompiler/DM/Expressions/Dereference.cs b/DMCompiler/DM/Expressions/Dereference.cs index 33553f9102..06338f2468 100644 --- a/DMCompiler/DM/Expressions/Dereference.cs +++ b/DMCompiler/DM/Expressions/Dereference.cs @@ -45,17 +45,17 @@ public sealed class CallOperation : NamedOperation { public required ArgumentList Parameters { get; init; } } - public readonly DMExpression Expression; public override DreamPath? Path { get; } public override DreamPath? NestedPath { get; } public override bool PathIsFuzzy => Path == null; public override DMComplexValueType ValType { get; } + private readonly DMExpression _expression; private readonly Operation[] _operations; - public Dereference(Location location, DreamPath? path, DMExpression expression, Operation[] operations) + public Dereference(DMObjectTree objectTree, Location location, DreamPath? path, DMExpression expression, Operation[] operations) : base(location, null) { - Expression = expression; + _expression = expression; Path = path; _operations = operations; @@ -64,16 +64,16 @@ public Dereference(Location location, DreamPath? path, DMExpression expression, } NestedPath = _operations[^1].Path; - ValType = DetermineValType(); + ValType = DetermineValType(objectTree); } - private DMComplexValueType DetermineValType() { - var type = Expression.ValType; + private DMComplexValueType DetermineValType(DMObjectTree objectTree) { + var type = _expression.ValType; var i = 0; while (!type.IsAnything && i < _operations.Length) { var operation = _operations[i++]; - if (type.TypePath is null || DMObjectTree.GetDMObject(type.TypePath.Value, false) is not { } dmObject) { + if (type.TypePath is null || !objectTree.TryGetDMObject(type.TypePath.Value, out var dmObject)) { // We're dereferencing something without a type-path, this could be anything type = DMValueType.Anything; break; @@ -103,31 +103,30 @@ private void ShortCircuitHandler(DMProc proc, string endLabel, ShortCircuitMode } } - private void EmitOperation(DMObject dmObject, DMProc proc, Operation operation, string endLabel, ShortCircuitMode shortCircuitMode) { + private void EmitOperation(ExpressionContext ctx, Operation operation, string endLabel, ShortCircuitMode shortCircuitMode) { if (operation.Safe) { - ShortCircuitHandler(proc, endLabel, shortCircuitMode); + ShortCircuitHandler(ctx.Proc, endLabel, shortCircuitMode); } switch (operation) { case FieldOperation fieldOperation: - proc.DereferenceField(fieldOperation.Identifier); + ctx.Proc.DereferenceField(fieldOperation.Identifier); break; case IndexOperation indexOperation: if (NestedPath is not null) { - var obj = DMObjectTree.GetDMObject(NestedPath.Value, false); - if (obj is not null && obj.IsSubtypeOf(DreamPath.Datum) && !obj.HasProc("operator[]")) { - DMCompiler.Emit(WarningCode.InvalidIndexOperation, Location, "Invalid index operation. datum[] index operations are not valid starting in BYOND 515.1641"); + if (ctx.ObjectTree.TryGetDMObject(NestedPath.Value, out var obj) && obj.IsSubtypeOf(DreamPath.Datum) && !obj.HasProc("operator[]")) { + ctx.Compiler.Emit(WarningCode.InvalidIndexOperation, Location, "Invalid index operation. datum[] index operations are not valid starting in BYOND 515.1641"); } } - indexOperation.Index.EmitPushValue(dmObject, proc); - proc.DereferenceIndex(); + indexOperation.Index.EmitPushValue(ctx); + ctx.Proc.DereferenceIndex(); break; case CallOperation callOperation: - var (argumentsType, argumentStackSize) = callOperation.Parameters.EmitArguments(dmObject, proc, null); - proc.DereferenceCall(callOperation.Identifier, argumentsType, argumentStackSize); + var (argumentsType, argumentStackSize) = callOperation.Parameters.EmitArguments(ctx, null); + ctx.Proc.DereferenceCall(callOperation.Identifier, argumentsType, argumentStackSize); break; default: @@ -135,28 +134,29 @@ private void EmitOperation(DMObject dmObject, DMProc proc, Operation operation, } } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - string endLabel = proc.NewLabelName(); + public override void EmitPushValue(ExpressionContext ctx) { + string endLabel = ctx.Proc.NewLabelName(); - Expression.EmitPushValue(dmObject, proc); + _expression.EmitPushValue(ctx); foreach (var operation in _operations) { - EmitOperation(dmObject, proc, operation, endLabel, ShortCircuitMode.KeepNull); + EmitOperation(ctx, operation, endLabel, ShortCircuitMode.KeepNull); } - proc.AddLabel(endLabel); + ctx.Proc.AddLabel(endLabel); } public override bool CanReferenceShortCircuit() { return _operations.Any(operation => operation.Safe); } - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { - Expression.EmitPushValue(dmObject, proc); + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + _expression.EmitPushValue(ctx); // Perform all except for our last operation for (int i = 0; i < _operations.Length - 1; i++) { - EmitOperation(dmObject, proc, _operations[i], endLabel, shortCircuitMode); + EmitOperation(ctx, _operations[i], endLabel, shortCircuitMode); } var operation = _operations[^1]; @@ -164,28 +164,27 @@ public override DMReference EmitReference(DMObject dmObject, DMProc proc, string switch (operation) { case FieldOperation fieldOperation: if (fieldOperation.Safe) { - ShortCircuitHandler(proc, endLabel, shortCircuitMode); + ShortCircuitHandler(ctx.Proc, endLabel, shortCircuitMode); } return DMReference.CreateField(fieldOperation.Identifier); case IndexOperation indexOperation: if (NestedPath is not null) { - var obj = DMObjectTree.GetDMObject(NestedPath.Value, false); - if (obj is not null && obj.IsSubtypeOf(DreamPath.Datum) && !obj.HasProc("operator[]=")) { - DMCompiler.Emit(WarningCode.InvalidIndexOperation, Location, "Invalid index operation. datum[] index operations are not valid starting in BYOND 515.1641"); + if (ctx.ObjectTree.TryGetDMObject(NestedPath.Value, out var obj) && obj.IsSubtypeOf(DreamPath.Datum) && !obj.HasProc("operator[]=")) { + ctx.Compiler.Emit(WarningCode.InvalidIndexOperation, Location, "Invalid index operation. datum[] index operations are not valid starting in BYOND 515.1641"); } } if (indexOperation.Safe) { - ShortCircuitHandler(proc, endLabel, shortCircuitMode); + ShortCircuitHandler(ctx.Proc, endLabel, shortCircuitMode); } - indexOperation.Index.EmitPushValue(dmObject, proc); + indexOperation.Index.EmitPushValue(ctx); return DMReference.ListIndex; case CallOperation: - DMCompiler.Emit(WarningCode.BadExpression, Location, + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "Expected field or index as reference, got proc call result"); return default; @@ -194,19 +193,19 @@ public override DMReference EmitReference(DMObject dmObject, DMProc proc, string } } - public override void EmitPushInitial(DMObject dmObject, DMProc proc) { - string endLabel = proc.NewLabelName(); + public override void EmitPushInitial(ExpressionContext ctx) { + string endLabel = ctx.Proc.NewLabelName(); - if (Expression is LValue exprLValue) { + if (_expression is LValue exprLValue) { // We don't want this instead pushing the constant value if it's const - exprLValue.EmitPushValueNoConstant(dmObject, proc); + exprLValue.EmitPushValueNoConstant(ctx); } else { - Expression.EmitPushValue(dmObject, proc); + _expression.EmitPushValue(ctx); } // Perform all except for our last operation for (int i = 0; i < _operations.Length - 1; i++) { - EmitOperation(dmObject, proc, _operations[i], endLabel, ShortCircuitMode.KeepNull); + EmitOperation(ctx, _operations[i], endLabel, ShortCircuitMode.KeepNull); } var operation = _operations[^1]; @@ -214,22 +213,24 @@ public override void EmitPushInitial(DMObject dmObject, DMProc proc) { switch (operation) { case FieldOperation fieldOperation: if (fieldOperation.Safe) { - proc.JumpIfNullNoPop(endLabel); + ctx.Proc.JumpIfNullNoPop(endLabel); } - proc.PushString(fieldOperation.Identifier); - proc.Initial(); + + ctx.Proc.PushString(fieldOperation.Identifier); + ctx.Proc.Initial(); break; case IndexOperation indexOperation: if (indexOperation.Safe) { - proc.JumpIfNullNoPop(endLabel); + ctx.Proc.JumpIfNullNoPop(endLabel); } - indexOperation.Index.EmitPushValue(dmObject, proc); - proc.Initial(); + + indexOperation.Index.EmitPushValue(ctx); + ctx.Proc.Initial(); break; case CallOperation: - DMCompiler.Emit(WarningCode.BadExpression, Location, + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "Expected field or index for initial(), got proc call result"); break; @@ -237,22 +238,22 @@ public override void EmitPushInitial(DMObject dmObject, DMProc proc) { throw new InvalidOperationException("Unimplemented dereference operation"); } - proc.AddLabel(endLabel); + ctx.Proc.AddLabel(endLabel); } - public void EmitPushIsSaved(DMObject dmObject, DMProc proc) { - string endLabel = proc.NewLabelName(); + public void EmitPushIsSaved(ExpressionContext ctx) { + string endLabel = ctx.Proc.NewLabelName(); - if (Expression is LValue exprLValue) { + if (_expression is LValue exprLValue) { // We don't want this instead pushing the constant value if it's const - exprLValue.EmitPushValueNoConstant(dmObject, proc); + exprLValue.EmitPushValueNoConstant(ctx); } else { - Expression.EmitPushValue(dmObject, proc); + _expression.EmitPushValue(ctx); } // Perform all except for our last operation for (int i = 0; i < _operations.Length - 1; i++) { - EmitOperation(dmObject, proc, _operations[i], endLabel, ShortCircuitMode.KeepNull); + EmitOperation(ctx, _operations[i], endLabel, ShortCircuitMode.KeepNull); } var operation = _operations[^1]; @@ -260,22 +261,24 @@ public void EmitPushIsSaved(DMObject dmObject, DMProc proc) { switch (operation) { case FieldOperation fieldOperation: if (fieldOperation.Safe) { - proc.JumpIfNullNoPop(endLabel); + ctx.Proc.JumpIfNullNoPop(endLabel); } - proc.PushString(fieldOperation.Identifier); - proc.IsSaved(); + + ctx.Proc.PushString(fieldOperation.Identifier); + ctx.Proc.IsSaved(); break; case IndexOperation indexOperation: if (indexOperation.Safe) { - proc.JumpIfNullNoPop(endLabel); + ctx.Proc.JumpIfNullNoPop(endLabel); } - indexOperation.Index.EmitPushValue(dmObject, proc); - proc.IsSaved(); + + indexOperation.Index.EmitPushValue(ctx); + ctx.Proc.IsSaved(); break; case CallOperation: - DMCompiler.Emit(WarningCode.BadExpression, Location, + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "Expected field or index for issaved(), got proc call result"); break; @@ -283,32 +286,25 @@ public void EmitPushIsSaved(DMObject dmObject, DMProc proc) { throw new InvalidOperationException("Unimplemented dereference operation"); } - proc.AddLabel(endLabel); + ctx.Proc.AddLabel(endLabel); } // BYOND says the nameof is invalid if the chain is not purely field operations - public override string? GetNameof(DMObject dmObject) { + public override string? GetNameof(ExpressionContext ctx) { return _operations.All(op => op is FieldOperation) ? ((FieldOperation)_operations[^1]).Identifier : null; } - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - var prevPath = _operations.Length == 1 ? Expression.Path : _operations[^2].Path; + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + var prevPath = _operations.Length == 1 ? _expression.Path : _operations[^2].Path; var operation = _operations[^1]; - if (operation is FieldOperation fieldOperation && prevPath is not null) { - var obj = DMObjectTree.GetDMObject(prevPath.Value); - var variable = obj!.GetVariable(fieldOperation.Identifier); - if (variable != null) { - if (variable.IsConst) - return variable.Value.TryAsConstant(out constant); - if (variable.ValType.IsCompileTimeReadOnly) { - variable.Value.TryAsConstant(out constant!); - return true; // MUST be true. - } - } + if (operation is FieldOperation fieldOperation && prevPath is not null && compiler.DMObjectTree.TryGetDMObject(prevPath.Value, out var obj)) { + var variable = obj.GetVariable(fieldOperation.Identifier); + if (variable is { CanConstFold: true }) + return variable.Value.TryAsConstant(compiler, out constant); } constant = null; @@ -318,8 +314,8 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { // expression::identifier // Same as initial(expression?.identifier) except this keeps its type -internal sealed class ScopeReference(Location location, DMExpression expression, string identifier, DMVariable dmVar) - : Initial(location, new Dereference(location, dmVar.Type, expression, // Just a little hacky +internal sealed class ScopeReference(DMObjectTree objectTree, Location location, DMExpression expression, string identifier, DMVariable dmVar) + : Initial(location, new Dereference(objectTree, location, dmVar.Type, expression, // Just a little hacky [ new Dereference.FieldOperation { Identifier = identifier, @@ -330,15 +326,14 @@ internal sealed class ScopeReference(Location location, DMExpression expression, ) { public override DreamPath? Path => Expression.Path; - public override string GetNameof(DMObject dmObject) => dmVar.Name; + public override string GetNameof(ExpressionContext ctx) => dmVar.Name; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (expression is not ConstantPath || dmVar.Value is not Constant varValue) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (expression is not IConstantPath) { constant = null; return false; } - constant = varValue; - return true; + return dmVar.Value!.TryAsConstant(compiler, out constant); } } diff --git a/DMCompiler/DM/Expressions/LValue.cs b/DMCompiler/DM/Expressions/LValue.cs index 57b4512dc6..a32902f4f9 100644 --- a/DMCompiler/DM/Expressions/LValue.cs +++ b/DMCompiler/DM/Expressions/LValue.cs @@ -7,34 +7,35 @@ namespace DMCompiler.DM.Expressions; internal abstract class LValue(Location location, DreamPath? path) : DMExpression(location) { public override DreamPath? Path { get; } = path; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - if (TryAsConstant(out var constant)) { // BYOND also seems to push consts instead of references when possible - constant.EmitPushValue(dmObject, proc); + public override void EmitPushValue(ExpressionContext ctx) { + if (TryAsConstant(ctx.Compiler, out var constant)) { // BYOND also seems to push consts instead of references when possible + constant.EmitPushValue(ctx); return; } - EmitPushValueNoConstant(dmObject, proc); + EmitPushValueNoConstant(ctx); } - public void EmitPushValueNoConstant(DMObject dmObject, DMProc proc) { - string endLabel = proc.NewLabelName(); + public void EmitPushValueNoConstant(ExpressionContext ctx) { + string endLabel = ctx.Proc.NewLabelName(); - DMReference reference = EmitReference(dmObject, proc, endLabel); - proc.PushReferenceValue(reference); + DMReference reference = EmitReference(ctx, endLabel); + ctx.Proc.PushReferenceValue(reference); - proc.AddLabel(endLabel); + ctx.Proc.AddLabel(endLabel); } - public virtual void EmitPushInitial(DMObject dmObject, DMProc proc) { - DMCompiler.Emit(WarningCode.BadExpression, Location, $"Can't get initial value of {this}"); - proc.Error(); + public virtual void EmitPushInitial(ExpressionContext ctx) { + ctx.Compiler.Emit(WarningCode.BadExpression, Location, $"Can't get initial value of {this}"); + ctx.Proc.Error(); } } // global internal class Global(Location location) : LValue(location, null) { - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { - DMCompiler.Emit(WarningCode.BadExpression, Location, "attempt to use `global` as a reference"); + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "attempt to use `global` as a reference"); return DMReference.Invalid; } } @@ -43,11 +44,12 @@ public override DMReference EmitReference(DMObject dmObject, DMProc proc, string internal sealed class Src(Location location, DreamPath? path) : LValue(location, path) { public override DMComplexValueType ValType => DMValueType.Anything; - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { return DMReference.Src; } - public override string GetNameof(DMObject dmObject) => "src"; + public override string GetNameof(ExpressionContext ctx) => "src"; } // usr @@ -55,20 +57,32 @@ internal sealed class Usr(Location location) : LValue(location, DreamPath.Mob) { //According to the docs, Usr is a mob. But it will get set to null by coders to clear refs. public override DMComplexValueType ValType => (DMValueType.Mob | DMValueType.Null); - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { return DMReference.Usr; } - public override string GetNameof(DMObject dmObject) => "usr"; + public override string GetNameof(ExpressionContext ctx) => "usr"; } // args internal sealed class Args(Location location) : LValue(location, DreamPath.List) { - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { return DMReference.Args; } - public override string GetNameof(DMObject dmObject) => "args"; + public override string GetNameof(ExpressionContext ctx) => "args"; +} + +// world +internal sealed class World(Location location) : LValue(location, DreamPath.World) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + return DMReference.World; + } + + public override string GetNameof(ExpressionContext ctx) => "world"; } // Identifier of local variable @@ -78,7 +92,8 @@ internal sealed class Local(Location location, DMProc.LocalVariable localVar) : // TODO: non-const local var static typing public override DMComplexValueType ValType => LocalVar.ExplicitValueType ?? DMValueType.Anything; - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { if (LocalVar.IsParameter) { return DMReference.CreateArgument(LocalVar.Id); } else { @@ -86,7 +101,7 @@ public override DMReference EmitReference(DMObject dmObject, DMProc proc, string } } - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { if (LocalVar is DMProc.LocalConstVariable constVar) { constant = constVar.Value; return true; @@ -96,23 +111,23 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { return false; } - public override void EmitPushInitial(DMObject dmObject, DMProc proc) { + public override void EmitPushInitial(ExpressionContext ctx) { // This happens silently in BYOND - DMCompiler.Emit(WarningCode.PointlessBuiltinCall, Location, "calling initial() on a local variable returns the current value"); - EmitPushValue(dmObject, proc); + ctx.Compiler.Emit(WarningCode.PointlessBuiltinCall, Location, "calling initial() on a local variable returns the current value"); + EmitPushValue(ctx); } - public override string GetNameof(DMObject dmObject) => LocalVar.Name; + public override string GetNameof(ExpressionContext ctx) => LocalVar.Name; } // Identifier of field internal sealed class Field(Location location, DMVariable variable, DMComplexValueType valType) : LValue(location, variable.Type) { public override DMComplexValueType ValType => valType; - public override void EmitPushInitial(DMObject dmObject, DMProc proc) { - proc.PushReferenceValue(DMReference.Src); - proc.PushString(variable.Name); - proc.Initial(); + public override void EmitPushInitial(ExpressionContext ctx) { + ctx.Proc.PushReferenceValue(DMReference.Src); + ctx.Proc.PushString(variable.Name); + ctx.Proc.Initial(); } public void EmitPushIsSaved(DMProc proc) { @@ -121,15 +136,16 @@ public void EmitPushIsSaved(DMProc proc) { proc.IsSaved(); } - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { return DMReference.CreateSrcField(variable.Name); } - public override string GetNameof(DMObject dmObject) => variable.Name; + public override string GetNameof(ExpressionContext ctx) => variable.Name; - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (variable is { IsConst: true, Value: not null }) { - return variable.Value.TryAsConstant(out constant); + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (variable is { CanConstFold: true, Value: not null }) { + return variable.Value.TryAsConstant(compiler, out constant); } constant = null; @@ -147,25 +163,26 @@ internal sealed class GlobalField(Location location, DreamPath? path, int id, D public override DMComplexValueType ValType => valType; - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { return DMReference.CreateGlobal(Id); } - public override void EmitPushInitial(DMObject dmObject, DMProc proc) { + public override void EmitPushInitial(ExpressionContext ctx) { // This happens silently in BYOND - DMCompiler.Emit(WarningCode.PointlessBuiltinCall, Location, "calling initial() on a global returns the current value"); - EmitPushValue(dmObject, proc); + ctx.Compiler.Emit(WarningCode.PointlessBuiltinCall, Location, "calling initial() on a global returns the current value"); + EmitPushValue(ctx); } - public override string GetNameof(DMObject dmObject) { - DMVariable global = DMObjectTree.Globals[Id]; + public override string GetNameof(ExpressionContext ctx) { + DMVariable global = ctx.ObjectTree.Globals[Id]; return global.Name; } - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - DMVariable global = DMObjectTree.Globals[Id]; - if (global.IsConst) { - return global.Value.TryAsConstant(out constant); + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + DMVariable global = compiler.DMObjectTree.Globals[Id]; + if (global.CanConstFold) { + return global.Value.TryAsConstant(compiler, out constant); } constant = null; @@ -174,9 +191,9 @@ public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { } internal sealed class GlobalVars(Location location) : LValue(location, null) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - proc.PushGlobalVars(); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Proc.PushGlobalVars(); } - public override string GetNameof(DMObject dmObject) => "vars"; + public override string GetNameof(ExpressionContext ctx) => "vars"; } diff --git a/DMCompiler/DM/Expressions/Procs.cs b/DMCompiler/DM/Expressions/Procs.cs index 0daf5ba614..ba5105dba0 100644 --- a/DMCompiler/DM/Expressions/Procs.cs +++ b/DMCompiler/DM/Expressions/Procs.cs @@ -6,26 +6,27 @@ namespace DMCompiler.DM.Expressions; // x() (only the identifier) internal sealed class Proc(Location location, string identifier) : DMExpression(location) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - DMCompiler.Emit(WarningCode.BadExpression, Location, "attempt to use proc as value"); - proc.Error(); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Compiler.Emit(WarningCode.BadExpression, Location, "attempt to use proc as value"); + ctx.Proc.Error(); } - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { - if (dmObject.HasProc(identifier)) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + if (ctx.Type.HasProc(identifier)) { return DMReference.CreateSrcProc(identifier); - } else if (DMObjectTree.TryGetGlobalProc(identifier, out var globalProc)) { + } else if (ctx.ObjectTree.TryGetGlobalProc(identifier, out var globalProc)) { return DMReference.CreateGlobalProc(globalProc.Id); } - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {dmObject.Path} does not have a proc named \"{identifier}\""); + ctx.Compiler.Emit(WarningCode.ItemDoesntExist, Location, $"Type {ctx.Type.Path} does not have a proc named \"{identifier}\""); //Just... pretend there is one for the sake of argument. return DMReference.CreateSrcProc(identifier); } - public DMProc? GetProc(DMObject dmObject) { + public DMProc? GetProc(DMCompiler compiler, DMObject dmObject) { var procId = dmObject.GetProcs(identifier)?[^1]; - return procId is null ? null : DMObjectTree.AllProcs[procId.Value]; + return procId is null ? null : compiler.DMObjectTree.AllProcs[procId.Value]; } public DMComplexValueType GetReturnType(DMObject dmObject) { @@ -33,33 +34,22 @@ public DMComplexValueType GetReturnType(DMObject dmObject) { } } -/// -/// This doesn't actually contain the GlobalProc itself; -/// this is just a hopped-up string that we eventually deference to get the real global proc during compilation. -/// -internal sealed class GlobalProc(Location location, string name) : DMExpression(location) { - public override DMComplexValueType ValType => GetProc().ReturnTypes; +internal sealed class GlobalProc(Location location, DMProc globalProc) : DMExpression(location) { + public override DMComplexValueType ValType => Proc.ReturnTypes; - public override string ToString() { - return $"{name}()"; - } + public DMProc Proc => globalProc; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - DMCompiler.Emit(WarningCode.InvalidReference, Location, $"Attempt to use proc \"{name}\" as value"); + public override string ToString() { + return $"{globalProc.Name}()"; } - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { - DMProc globalProc = GetProc(); - return DMReference.CreateGlobalProc(globalProc.Id); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Compiler.Emit(WarningCode.InvalidReference, Location, $"Attempt to use proc \"{this}\" as value"); } - public DMProc GetProc() { - if (!DMObjectTree.TryGetGlobalProc(name, out var globalProc)) { - DMCompiler.Emit(WarningCode.ItemDoesntExist, Location, $"No global proc named \"{name}\""); - return DMObjectTree.GlobalInitProc; // Just give this, who cares - } - - return globalProc; + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + return DMReference.CreateGlobalProc(Proc.Id); } } @@ -67,27 +57,28 @@ public DMProc GetProc() { /// .
/// This is an LValue _and_ a proc! ///
-internal sealed class ProcSelf(Location location, DreamPath? path, DMProc proc) : LValue(location, path) { - public override DMComplexValueType ValType => proc.ReturnTypes; +internal sealed class ProcSelf(Location location, DMComplexValueType valType) : LValue(location, null) { + public override DMComplexValueType ValType => valType; - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { return DMReference.Self; } } // .. -internal sealed class ProcSuper(Location location, DMObject _dmObject, DMProc _proc) : DMExpression(location) { - public override DMComplexValueType ValType => _dmObject.GetProcReturnTypes(_proc.Name) ?? DMValueType.Anything; +internal sealed class ProcSuper(Location location, DMComplexValueType? valType) : DMExpression(location) { + public override DMComplexValueType ValType => valType ?? DMValueType.Anything; - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - DMCompiler.Emit(WarningCode.InvalidReference, Location, $"Attempt to use proc \"..\" as value"); + public override void EmitPushValue(ExpressionContext ctx) { + ctx.Compiler.Emit(WarningCode.InvalidReference, Location, $"Attempt to use proc \"..\" as value"); } - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { - if ((proc.Attributes & ProcAttributes.IsOverride) != ProcAttributes.IsOverride) { + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + if ((ctx.Proc.Attributes & ProcAttributes.IsOverride) != ProcAttributes.IsOverride) { // Don't emit if lateral proc overrides exist - if (dmObject.GetProcs(proc.Name)!.Count == 1) { - DMCompiler.Emit(WarningCode.PointlessParentCall, Location, + if (ctx.Type.GetProcs(ctx.Proc.Name)!.Count == 1) { + ctx.Compiler.Emit(WarningCode.PointlessParentCall, Location, "Calling parents via ..() in a proc definition does nothing"); } } @@ -102,10 +93,10 @@ internal sealed class ProcCall(Location location, DMExpression target, ArgumentL public override bool PathIsFuzzy => Path == null; public override DMComplexValueType ValType => valType.IsAnything ? target.ValType : valType; - public (DMObject? ProcOwner, DMProc? Proc) GetTargetProc(DMObject dmObject) { + public (DMObject? ProcOwner, DMProc? Proc) GetTargetProc(DMCompiler compiler, DMObject dmObject) { return target switch { - Proc procTarget => (dmObject, procTarget.GetProc(dmObject)), - GlobalProc procTarget => (null, procTarget.GetProc()), + Proc procTarget => (dmObject, procTarget.GetProc(compiler, dmObject)), + GlobalProc procTarget => (null, procTarget.Proc), _ => (null, null) }; } @@ -114,14 +105,14 @@ public override string ToString() { return target.ToString()!; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - (DMObject? procOwner, DMProc? targetProc) = GetTargetProc(dmObject); - DoCompileTimeLinting(procOwner, targetProc); + public override void EmitPushValue(ExpressionContext ctx) { + (DMObject? procOwner, DMProc? targetProc) = GetTargetProc(ctx.Compiler, ctx.Type); + DoCompileTimeLinting(ctx.Compiler, procOwner, targetProc); if ((targetProc?.Attributes & ProcAttributes.Unimplemented) == ProcAttributes.Unimplemented) { - DMCompiler.UnimplementedWarning(Location, $"{procOwner?.Path.ToString() ?? "/"}.{targetProc.Name}() is not implemented"); + ctx.Compiler.UnimplementedWarning(Location, $"{procOwner?.Path.ToString() ?? "/"}.{targetProc.Name}() is not implemented"); } - string endLabel = proc.NewLabelName(); + string endLabel = ctx.Proc.NewLabelName(); DMCallArgumentsType argumentsType; int argumentStackSize; @@ -129,20 +120,20 @@ public override void EmitPushValue(DMObject dmObject, DMProc proc) { argumentsType = DMCallArgumentsType.FromProcArguments; argumentStackSize = 0; } else { - (argumentsType, argumentStackSize) = arguments.EmitArguments(dmObject, proc, targetProc); + (argumentsType, argumentStackSize) = arguments.EmitArguments(ctx, targetProc); } - DMReference procRef = target.EmitReference(dmObject, proc, endLabel); + DMReference procRef = target.EmitReference(ctx, endLabel); - proc.Call(procRef, argumentsType, argumentStackSize); - proc.AddLabel(endLabel); + ctx.Proc.Call(procRef, argumentsType, argumentStackSize); + ctx.Proc.AddLabel(endLabel); } /// /// This is a good place to do some compile-time linting of any native procs that require it, /// such as native procs that check ahead of time if the number of arguments is correct (like matrix() or sin()) /// - private void DoCompileTimeLinting(DMObject? procOwner, DMProc? targetProc) { + private void DoCompileTimeLinting(DMCompiler compiler, DMObject? procOwner, DMProc? targetProc) { if(procOwner is null || procOwner.Path == DreamPath.Root) { if (targetProc is null) return; @@ -156,12 +147,13 @@ private void DoCompileTimeLinting(DMObject? procOwner, DMProc? targetProc) { case 3: // These imply that they're trying to use the undocumented matrix signatures. case 4: // The lint is to just check that the last argument is a numeric constant that is a valid matrix "opcode." var lastArg = arguments.Expressions.Last().Expr; - if(lastArg.TryAsConstant(out var constant)) { + if(lastArg.TryAsConstant(compiler, out var constant)) { if(constant is not Number opcodeNumber) { - DMCompiler.Emit(WarningCode.SuspiciousMatrixCall, arguments.Location, + compiler.Emit(WarningCode.SuspiciousMatrixCall, arguments.Location, "Arguments for matrix() are invalid - either opcode is invalid or not enough arguments"); break; } + //Note that it is possible for the numeric value to not be an opcode itself, //but the call is still valid. //This is because of MATRIX_MODIFY; things like MATRIX_INVERT | MATRIX_MODIFY are okay! @@ -170,28 +162,28 @@ private void DoCompileTimeLinting(DMObject? procOwner, DMProc? targetProc) { //NOTE: This still does let some certain weird opcodes through, //like a MODIFY with no other operation present. //Not sure if that is a parity behaviour or not! - DMCompiler.Emit(WarningCode.SuspiciousMatrixCall, arguments.Location, + compiler.Emit(WarningCode.SuspiciousMatrixCall, arguments.Location, "Arguments for matrix() are invalid - either opcode is invalid or not enough arguments"); } } + break; case 5: // BYOND always runtimes but DOES compile, here - DMCompiler.Emit(WarningCode.SuspiciousMatrixCall, arguments.Location, - $"Calling matrix() with 5 arguments will always error when called at runtime"); + compiler.Emit(WarningCode.SuspiciousMatrixCall, arguments.Location, + "Calling matrix() with 5 arguments will always error when called at runtime"); break; default: // BYOND always compiletimes here - DMCompiler.Emit(WarningCode.InvalidArgumentCount, arguments.Location, + compiler.Emit(WarningCode.InvalidArgumentCount, arguments.Location, $"Too many arguments to matrix() - got {arguments.Length} arguments, expecting 6 or less"); break; - } } } } - public override bool TryAsJsonRepresentation(out object? json) { + public override bool TryAsJsonRepresentation(DMCompiler compiler, out object? json) { json = null; - DMCompiler.UnimplementedWarning(Location, $"DMM overrides for expression {GetType()} are not implemented"); + compiler.UnimplementedWarning(Location, $"DMM overrides for expression {GetType()} are not implemented"); return true; //TODO } } diff --git a/DMCompiler/DM/Expressions/Ternary.cs b/DMCompiler/DM/Expressions/Ternary.cs index 27bc106657..c5b874a89b 100644 --- a/DMCompiler/DM/Expressions/Ternary.cs +++ b/DMCompiler/DM/Expressions/Ternary.cs @@ -1,61 +1,46 @@ using System.Diagnostics.CodeAnalysis; -using DMCompiler.Compiler; namespace DMCompiler.DM.Expressions; // x ? y : z -internal sealed class Ternary : DMExpression { - private readonly DMExpression _a, _b, _c; - +internal sealed class Ternary(Location location, DMExpression a, DMExpression b, DMExpression c) + : DMExpression(location) { public override bool PathIsFuzzy => true; - public override DMComplexValueType ValType { get; } - - public Ternary(Location location, DMExpression a, DMExpression b, DMExpression c) : base(location) { - _a = a; - _b = b; - _c = c; - - if (b.ValType.TypePath != null && c.ValType.TypePath != null && b.ValType.TypePath != c.ValType.TypePath) { - DMCompiler.Emit(WarningCode.LostTypeInfo, Location, - $"Ternary has type paths {b.ValType.TypePath} and {c.ValType.TypePath} but a value can only have one type path. Using {b.ValType.TypePath}."); - } - - ValType = new(b.ValType.Type | c.ValType.Type, b.ValType.TypePath ?? c.ValType.TypePath); - } + public override DMComplexValueType ValType { get; } = new(b.ValType.Type | c.ValType.Type, b.ValType.TypePath ?? c.ValType.TypePath); - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!_a.TryAsConstant(out var constant1)) { + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!a.TryAsConstant(compiler, out var constant1)) { constant = null; return false; } if (constant1.IsTruthy()) { - return _b.TryAsConstant(out constant); + return b.TryAsConstant(compiler, out constant); } - return _c.TryAsConstant(out constant); + return c.TryAsConstant(compiler, out constant); } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - string cLabel = proc.NewLabelName(); - string endLabel = proc.NewLabelName(); - - _a.EmitPushValue(dmObject, proc); - proc.JumpIfFalse(cLabel); - _b.EmitPushValue(dmObject, proc); - proc.Jump(endLabel); - proc.AddLabel(cLabel); - _c.EmitPushValue(dmObject, proc); - proc.AddLabel(endLabel); + public override void EmitPushValue(ExpressionContext ctx) { + string cLabel = ctx.Proc.NewLabelName(); + string endLabel = ctx.Proc.NewLabelName(); + + a.EmitPushValue(ctx); + ctx.Proc.JumpIfFalse(cLabel); + b.EmitPushValue(ctx); + ctx.Proc.Jump(endLabel); + ctx.Proc.AddLabel(cLabel); + c.EmitPushValue(ctx); + ctx.Proc.AddLabel(endLabel); } } // var in x to y internal sealed class InRange(Location location, DMExpression var, DMExpression start, DMExpression end) : DMExpression(location) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - var.EmitPushValue(dmObject, proc); - start.EmitPushValue(dmObject, proc); - end.EmitPushValue(dmObject, proc); - proc.IsInRange(); + public override void EmitPushValue(ExpressionContext ctx) { + var.EmitPushValue(ctx); + start.EmitPushValue(ctx); + end.EmitPushValue(ctx); + ctx.Proc.IsInRange(); } } diff --git a/DMCompiler/DM/Expressions/Unary.cs b/DMCompiler/DM/Expressions/Unary.cs index 3f53b2f716..f6b07c94fb 100644 --- a/DMCompiler/DM/Expressions/Unary.cs +++ b/DMCompiler/DM/Expressions/Unary.cs @@ -9,67 +9,67 @@ internal abstract class UnaryOp(Location location, DMExpression expr) : DMExpres // -x internal sealed class Negate(Location location, DMExpression expr) : UnaryOp(location, expr) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!Expr.TryAsConstant(out constant) || constant is not Number number) + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!Expr.TryAsConstant(compiler, out constant) || constant is not Number number) return false; constant = new Number(Location, -number.Value); return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - Expr.EmitPushValue(dmObject, proc); - proc.Negate(); + public override void EmitPushValue(ExpressionContext ctx) { + Expr.EmitPushValue(ctx); + ctx.Proc.Negate(); } } // !x internal sealed class Not(Location location, DMExpression expr) : UnaryOp(location, expr) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!Expr.TryAsConstant(out constant)) return false; + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!Expr.TryAsConstant(compiler, out constant)) return false; constant = new Number(Location, constant.IsTruthy() ? 0 : 1); return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - Expr.EmitPushValue(dmObject, proc); - proc.Not(); + public override void EmitPushValue(ExpressionContext ctx) { + Expr.EmitPushValue(ctx); + ctx.Proc.Not(); } } // ~x internal sealed class BinaryNot(Location location, DMExpression expr) : UnaryOp(location, expr) { - public override bool TryAsConstant([NotNullWhen(true)] out Constant? constant) { - if (!Expr.TryAsConstant(out constant) || constant is not Number constantNum) + public override bool TryAsConstant(DMCompiler compiler, [NotNullWhen(true)] out Constant? constant) { + if (!Expr.TryAsConstant(compiler, out constant) || constant is not Number constantNum) return false; constant = new Number(Location, ~(int)constantNum.Value); return true; } - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - Expr.EmitPushValue(dmObject, proc); - proc.BinaryNot(); + public override void EmitPushValue(ExpressionContext ctx) { + Expr.EmitPushValue(ctx); + ctx.Proc.BinaryNot(); } } internal abstract class AssignmentUnaryOp(Location location, DMExpression expr) : UnaryOp(location, expr) { - protected abstract void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel); + protected abstract void EmitOp(DMProc proc, DMReference reference); - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - string endLabel = proc.NewLabelName(); + public override void EmitPushValue(ExpressionContext ctx) { + string endLabel = ctx.Proc.NewLabelName(); - DMReference reference = Expr.EmitReference(dmObject, proc, endLabel); - EmitOp(dmObject, proc, reference, endLabel); + DMReference reference = Expr.EmitReference(ctx, endLabel); + EmitOp(ctx.Proc, reference); - proc.AddLabel(endLabel); + ctx.Proc.AddLabel(endLabel); } } // ++x internal sealed class PreIncrement(Location location, DMExpression expr) : AssignmentUnaryOp(location, expr) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { + protected override void EmitOp(DMProc proc, DMReference reference) { proc.PushFloat(1); proc.Append(reference); } @@ -77,14 +77,14 @@ protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference refer // x++ internal sealed class PostIncrement(Location location, DMExpression expr) : AssignmentUnaryOp(location, expr) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { + protected override void EmitOp(DMProc proc, DMReference reference) { proc.Increment(reference); } } // --x internal sealed class PreDecrement(Location location, DMExpression expr) : AssignmentUnaryOp(location, expr) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { + protected override void EmitOp(DMProc proc, DMReference reference) { proc.PushFloat(1); proc.Remove(reference); } @@ -92,31 +92,33 @@ protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference refer // x-- internal sealed class PostDecrement(Location location, DMExpression expr) : AssignmentUnaryOp(location, expr) { - protected override void EmitOp(DMObject dmObject, DMProc proc, DMReference reference, string endLabel) { + protected override void EmitOp(DMProc proc, DMReference reference) { proc.Decrement(reference); } } // &x internal sealed class PointerRef(Location location, DMExpression expr) : UnaryOp(location, expr) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - Expr.EmitPushValue(dmObject, proc); - DMCompiler.UnimplementedWarning(Location, "Pointers are currently unimplemented and identifiers will be treated as normal variables."); + public override void EmitPushValue(ExpressionContext ctx) { + Expr.EmitPushValue(ctx); + ctx.Compiler.UnimplementedWarning(Location, "Pointers are currently unimplemented and identifiers will be treated as normal variables."); } - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { - return Expr.EmitReference(dmObject, proc, endLabel, shortCircuitMode); + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + return Expr.EmitReference(ctx, endLabel, shortCircuitMode); } } // *x internal sealed class PointerDeref(Location location, DMExpression expr) : UnaryOp(location, expr) { - public override void EmitPushValue(DMObject dmObject, DMProc proc) { - Expr.EmitPushValue(dmObject, proc); - DMCompiler.UnimplementedWarning(Location, "Pointers are currently unimplemented and identifiers will be treated as normal variables."); + public override void EmitPushValue(ExpressionContext ctx) { + Expr.EmitPushValue(ctx); + ctx.Compiler.UnimplementedWarning(Location, "Pointers are currently unimplemented and identifiers will be treated as normal variables."); } - public override DMReference EmitReference(DMObject dmObject, DMProc proc, string endLabel, ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { - return Expr.EmitReference(dmObject, proc, endLabel, shortCircuitMode); + public override DMReference EmitReference(ExpressionContext ctx, string endLabel, + ShortCircuitMode shortCircuitMode = ShortCircuitMode.KeepNull) { + return Expr.EmitReference(ctx, endLabel, shortCircuitMode); } } diff --git a/DMCompiler/DMCompiler.cs b/DMCompiler/DMCompiler.cs index 24a73bdbc4..65cac0c3b9 100644 --- a/DMCompiler/DMCompiler.cs +++ b/DMCompiler/DMCompiler.cs @@ -17,19 +17,31 @@ namespace DMCompiler; -//TODO: Make this not a static class -public static class DMCompiler { - public static int ErrorCount; - public static int WarningCount; - public static HashSet UniqueEmissions = new(); - public static DMCompilerSettings Settings; - public static IReadOnlyList ResourceDirectories => _resourceDirectories; - - private static readonly DMCompilerConfiguration Config = new(); - private static readonly List _resourceDirectories = new(); - private static DateTime _compileStartTime; - - public static bool Compile(DMCompilerSettings settings) { +public class DMCompiler { + public int ErrorCount; + public int WarningCount; + public HashSet UniqueEmissions = new(); + public DMCompilerSettings Settings; + public IReadOnlyList ResourceDirectories => _resourceDirectories; + + private readonly DMCompilerConfiguration Config = new(); + private readonly List _resourceDirectories = new(); + private DateTime _compileStartTime; + + internal readonly DMCodeTree DMCodeTree; + internal readonly DMObjectTree DMObjectTree; + internal readonly DMProc GlobalInitProc; + + public DMCompiler() { + DMCodeTree = new(this); + DMObjectTree = new(this); + GlobalInitProc = new(this, -1, DMObjectTree.Root, null); + } + + public bool Compile(DMCompilerSettings settings) { + if (_compileStartTime != default) + throw new Exception("Create a new DMCompiler to compile again"); + ErrorCount = 0; WarningCount = 0; UniqueEmissions.Clear(); @@ -51,13 +63,13 @@ public static bool Compile(DMCompilerSettings settings) { ForcedWarning("Unimplemented proc & var warnings are currently suppressed"); } - DMPreprocessor preprocessor = Preprocess(settings.Files, settings.MacroDefines); + DMPreprocessor preprocessor = Preprocess(this, settings.Files, settings.MacroDefines); bool successfulCompile = preprocessor is not null && Compile(preprocessor); 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); + List maps = ConvertMaps(this, preprocessor.IncludedMaps); if (ErrorCount > 0) { successfulCompile = false; @@ -82,20 +94,21 @@ public static bool Compile(DMCompilerSettings settings) { return successfulCompile; } - public static void AddResourceDirectory(string dir) { + public void AddResourceDirectory(string dir) { dir = dir.Replace('\\', Path.DirectorySeparatorChar); _resourceDirectories.Add(dir); } - private static DMPreprocessor? Preprocess(List files, Dictionary? macroDefines) { + private DMPreprocessor? Preprocess(DMCompiler compiler, List files, Dictionary? macroDefines) { DMPreprocessor? Build() { - DMPreprocessor preproc = new DMPreprocessor(true); + DMPreprocessor preproc = new DMPreprocessor(compiler, true); if (macroDefines != null) { foreach (var (key, value) in macroDefines) { preproc.DefineMacro(key, value); } } + DefineFatalErrors(); // NB: IncludeFile pushes newly seen files to a stack, so push @@ -109,7 +122,7 @@ public static void AddResourceDirectory(string dir) { string includeDir = Path.GetDirectoryName(files[i]); string fileName = Path.GetFileName(files[i]); - preproc.IncludeFile(includeDir, fileName); + preproc.IncludeFile(includeDir, fileName, false); } string compilerDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty; @@ -117,7 +130,7 @@ public static void AddResourceDirectory(string dir) { // Push DMStandard to the top of the stack, prioritizing it. if (!Settings.NoStandard) { - preproc.IncludeFile(dmStandardDirectory, "_Standard.dm"); + preproc.IncludeFile(dmStandardDirectory, "_Standard.dm", true); } // Push the pragma config file to the tippy-top of the stack, super-duper prioritizing it, since it governs some compiler behaviour. @@ -136,7 +149,7 @@ public static void AddResourceDirectory(string dir) { return null; } - preproc.IncludeFile(pragmaDirectory,pragmaName); + preproc.IncludeFile(pragmaDirectory, pragmaName, true); return preproc; } @@ -157,9 +170,9 @@ public static void AddResourceDirectory(string dir) { return Build(); } - private static bool Compile(IEnumerable preprocessedTokens) { + private bool Compile(IEnumerable preprocessedTokens) { DMLexer dmLexer = new DMLexer(null, preprocessedTokens); - DMParser dmParser = new DMParser(dmLexer); + DMParser dmParser = new DMParser(this, dmLexer); VerbosePrint("Parsing"); DMASTFile astFile = dmParser.File(); @@ -168,12 +181,13 @@ private static bool Compile(IEnumerable preprocessedTokens) { VerbosePrint("Constant folding"); astSimplifier.FoldAst(astFile); - DMObjectBuilder.BuildObjectTree(astFile); + DMCodeTreeBuilder dmCodeTreeBuilder = new(this); + dmCodeTreeBuilder.BuildCodeTree(astFile); return ErrorCount == 0; } - public static void Emit(CompilerEmission emission) { + public void Emit(CompilerEmission emission) { switch (emission.Level) { case ErrorLevel.Disabled: return; @@ -195,7 +209,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) { + public bool Emit(WarningCode code, Location loc, string message) { ErrorLevel level = Config.ErrorConfig[code]; Emit(new CompilerEmission(level, code, loc, message)); return level == ErrorLevel.Error; @@ -205,12 +219,12 @@ public static bool Emit(WarningCode code, Location loc, string message) { /// To be used when the compiler MUST ALWAYS give an error.
/// Completely ignores the warning configuration. Use wisely! /// - public static void ForcedError(string message) { + public void ForcedError(string message) { ForcedError(Location.Internal, message); } /// - public static void ForcedError(Location loc, string message) { + public void ForcedError(Location loc, string message) { Console.WriteLine(new CompilerEmission(ErrorLevel.Error, loc, message).ToString()); ErrorCount++; } @@ -219,43 +233,43 @@ public static void ForcedError(Location loc, string message) { /// To be used when the compiler MUST ALWAYS give a warning.
/// Completely ignores the warning configuration. Use wisely! /// - public static void ForcedWarning(string message) { + public void ForcedWarning(string message) { Console.WriteLine(new CompilerEmission(ErrorLevel.Warning, Location.Internal, message).ToString()); WarningCount++; } /// - public static void ForcedWarning(Location loc, string message) { + public void ForcedWarning(Location loc, string message) { Console.WriteLine(new CompilerEmission(ErrorLevel.Warning, loc, message).ToString()); WarningCount++; } - public static void UnimplementedWarning(Location loc, string message) { + public void UnimplementedWarning(Location loc, string message) { if (Settings.SuppressUnimplementedWarnings) return; Emit(WarningCode.UnimplementedAccess, loc, message); } - public static void VerbosePrint(string message) { + public void VerbosePrint(string message) { if (!Settings.Verbose) return; TimeSpan duration = DateTime.Now - _compileStartTime; Console.WriteLine($"{duration.ToString(@"mm\:ss\.fffffff")}: {message}"); } - private static List ConvertMaps(List mapPaths) { + private List ConvertMaps(DMCompiler compiler, List mapPaths) { List maps = new(); int zOffset = 0; foreach (string mapPath in mapPaths) { VerbosePrint($"Converting map {mapPath}"); - DMPreprocessor preprocessor = new DMPreprocessor(false); - preprocessor.PreprocessFile(Path.GetDirectoryName(mapPath), Path.GetFileName(mapPath)); + DMPreprocessor preprocessor = new DMPreprocessor(compiler, false); + preprocessor.PreprocessFile(Path.GetDirectoryName(mapPath), Path.GetFileName(mapPath), false); DMLexer lexer = new DMLexer(mapPath, preprocessor); - DMMParser parser = new DMMParser(lexer, zOffset); + DMMParser parser = new DMMParser(this, lexer, zOffset); DreamMapJson map = parser.ParseMap(); zOffset = Math.Max(zOffset + 1, map.MaxZ); @@ -265,7 +279,7 @@ private static List ConvertMaps(List mapPaths) { return maps; } - private static string SaveJson(List maps, string interfaceFile, string outputFile) { + private string SaveJson(List maps, string interfaceFile, string outputFile) { var jsonRep = DMObjectTree.CreateJsonRepresentation(); var compiledDream = new DreamCompiledJson { Metadata = new DreamCompiledJsonMetadata { Version = OpcodeVerifier.GetOpcodesHash() }, @@ -279,8 +293,8 @@ private static string SaveJson(List maps, string interfaceFile, st Procs = jsonRep.Item2 }; - if (DMObjectTree.GlobalInitProc.AnnotatedBytecode.GetLength() > 0) - compiledDream.GlobalInitProc = DMObjectTree.GlobalInitProc.GetJsonRepresentation(); + if (GlobalInitProc.AnnotatedBytecode.GetLength() > 0) + compiledDream.GlobalInitProc = GlobalInitProc.GetJsonRepresentation(); if (DMObjectTree.Globals.Count > 0) { GlobalListJson globalListJson = new GlobalListJson { @@ -298,7 +312,7 @@ private static string SaveJson(List maps, string interfaceFile, st DMVariable global = DMObjectTree.Globals[i]; globalListJson.Names.Add(global.Name); - if (!global.TryAsJsonRepresentation(out var globalJson)) + if (!global.TryAsJsonRepresentation(this, out var globalJson)) ForcedError(global.Value.Location, $"Failed to serialize global {global.Name}"); if (globalJson != null) { @@ -330,7 +344,7 @@ private static string SaveJson(List maps, string interfaceFile, st return string.Empty; } - public static void DefineFatalErrors() { + public void DefineFatalErrors() { foreach (WarningCode code in Enum.GetValues()) { if((int)code < 1_000) { Config.ErrorConfig[code] = ErrorLevel.Error; @@ -341,7 +355,7 @@ public static void DefineFatalErrors() { /// /// This method also enforces the rule that all emissions with codes less than 1000 are mandatory errors. /// - public static void CheckAllPragmasWereSet() { + public void CheckAllPragmasWereSet() { foreach(WarningCode code in Enum.GetValues()) { if (!Config.ErrorConfig.ContainsKey(code)) { ForcedWarning($"Warning #{(int)code:d4} '{code.ToString()}' was never declared as error, warning, notice, or disabled."); @@ -350,11 +364,11 @@ public static void CheckAllPragmasWereSet() { } } - public static void SetPragma(WarningCode code, ErrorLevel level) { + public void SetPragma(WarningCode code, ErrorLevel level) { Config.ErrorConfig[code] = level; } - public static ErrorLevel CodeToLevel(WarningCode code) { + public ErrorLevel CodeToLevel(WarningCode code) { if (!Config.ErrorConfig.TryGetValue(code, out var ret)) throw new Exception($"Failed to find error level for code {code}"); @@ -371,6 +385,7 @@ public struct DMCompilerSettings { public bool DumpPreprocessor = false; public bool NoStandard = false; public bool Verbose = false; + public bool PrintCodeTree = false; public Dictionary? MacroDefines = null; /// A user-provided pragma config file, if one was provided. public string? PragmaFileOverride = null; diff --git a/DMCompiler/DMStandard/DefaultPragmaConfig.dm b/DMCompiler/DMStandard/DefaultPragmaConfig.dm index c67eb91610..f6adf4e312 100644 --- a/DMCompiler/DMStandard/DefaultPragmaConfig.dm +++ b/DMCompiler/DMStandard/DefaultPragmaConfig.dm @@ -32,6 +32,7 @@ #pragma SuspiciousSwitchCase warning #pragma PointlessPositionalArgument warning #pragma ProcArgumentGlobal warning // Ref BYOND issue https://www.byond.com/forum/post/2830750 +#pragma AmbiguousVarStatic warning // https://github.com/OpenDreamProject/OpenDream/issues/997 // NOTE: The next few pragmas are for OpenDream's experimental type checker // This feature is still in development, elevating these pragmas outside of local testing is discouraged // An RFC to finalize this feature is coming soon(TM) @@ -48,6 +49,7 @@ #pragma EmptyBlock notice #pragma EmptyProc disabled // NOTE: If you enable this in OD's default pragma config file, it will emit for OD's DMStandard. Put it in your codebase's pragma config file. #pragma UnsafeClientAccess disabled // NOTE: Only checks for unsafe accesses like "client.foobar" and doesn't consider if the client was already null-checked earlier in the proc -#pragma AssignmentInConditional warning +#pragma AssignmentInConditional warning #pragma PickWeightedSyntax disabled #pragma AmbiguousInOrder warning +#pragma RuntimeSearchOperator disabled diff --git a/DMCompiler/DMStandard/Defines.dm b/DMCompiler/DMStandard/Defines.dm index fcd13bf77d..148660d34c 100644 --- a/DMCompiler/DMStandard/Defines.dm +++ b/DMCompiler/DMStandard/Defines.dm @@ -60,8 +60,8 @@ #define SYNC_STEPS 3 //world.system_type -#define UNIX 0 -#define MS_WINDOWS 1 +#define UNIX "UNIX" +#define MS_WINDOWS "MS_WINDOWS" //Icon blending functions #define ICON_ADD 0 diff --git a/DMCompiler/DMStandard/Types/Client.dm b/DMCompiler/DMStandard/Types/Client.dm index 041b34e8aa..136e2ee2fb 100644 --- a/DMCompiler/DMStandard/Types/Client.dm +++ b/DMCompiler/DMStandard/Types/Client.dm @@ -11,7 +11,7 @@ var/tag var/const/type = /client - var/mob/mob as /mob|null + var/mob/mob // TODO: as /mob|null var/atom/eye var/lazy_eye = 0 as opendream_unimplemented var/perspective = MOB_PERSPECTIVE diff --git a/DMCompiler/DMStandard/Types/World.dm b/DMCompiler/DMStandard/Types/World.dm index 39f7242d4c..609109c1d8 100644 --- a/DMCompiler/DMStandard/Types/World.dm +++ b/DMCompiler/DMStandard/Types/World.dm @@ -42,7 +42,7 @@ var/sleep_offline = 0 - var/system_type + var/const/system_type as opendream_noconstfold var/map_cpu = 0 as opendream_unimplemented var/hub as opendream_unimplemented @@ -56,7 +56,7 @@ // An OpenDream read-only var that tells you what port Topic() is listening on // Remove OPENDREAM_TOPIC_PORT_EXISTS if this is ever removed - var/const/opendream_topic_port + var/const/opendream_topic_port as opendream_noconstfold proc/New() proc/Del() diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index efa069e43d..614ef790b2 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -1,5 +1,3 @@ -/var/world/world = /world as /world // Set to /world to suppress issues with Typemaker - //These procs should be in alphabetical order, as in DreamProcNativeRoot.cs proc/alert(Usr = usr, Message, Title, Button1 = "Ok", Button2, Button3) as text proc/animate(Object, time, loop, easing, flags, delay, pixel_x, pixel_y, pixel_z, maptext, maptext_width, maptext_height, maptext_x, maptext_y, dir, alpha, transform, color, luminosity, infra_luminosity, layer, glide_size, icon, icon_state, invisibility, suffix) as null @@ -22,9 +20,10 @@ proc/file2text(File) as text|null proc/filter(type, ...) proc/findtext(Haystack, Needle, Start = 1, End = 0) as num proc/findtextEx(Haystack, Needle, Start = 1, End = 0) as num -proc/findlasttext(Haystack, Needle, Start = 1, End = 0) as num -proc/findlasttextEx(Haystack, Needle, Start = 1, End = 0) as num +proc/findlasttext(Haystack, Needle, Start = 0, End = 1) as num +proc/findlasttextEx(Haystack, Needle, Start = 0, End = 1) as num proc/flick(Icon, Object) + set opendream_unimplemented = 1 proc/flist(Path) as /list proc/floor(A) as num proc/fract(n) as num diff --git a/DMCompiler/DreamPath.cs b/DMCompiler/DreamPath.cs index d8182693af..1812e745a3 100644 --- a/DMCompiler/DreamPath.cs +++ b/DMCompiler/DreamPath.cs @@ -94,9 +94,8 @@ public DreamPath(PathType type, string[] elements) { Normalize(true); } - public DMValueType GetAtomType() { - var dmType = DMObjectTree.GetDMObject(this, false); - if (dmType is null) + internal DMValueType GetAtomType(DMCompiler compiler) { + if (!compiler.DMObjectTree.TryGetDMObject(this, out var dmType)) return DMValueType.Anything; if (dmType.IsSubtypeOf(Obj)) diff --git a/DMCompiler/Location.cs b/DMCompiler/Location.cs index e4895b167a..4fab0e9378 100644 --- a/DMCompiler/Location.cs +++ b/DMCompiler/Location.cs @@ -2,12 +2,12 @@ namespace DMCompiler; -public readonly struct Location(string filePath, int? line, int? column) { +public readonly struct Location(string filePath, int? line, int? column, bool inDMStandard = false) { /// /// For when DM location information can't be determined. /// public static readonly Location Unknown = new(); - + /// /// For when internal OpenDream warnings/errors are raised or something internal needs to be passed a location. /// @@ -16,6 +16,7 @@ public readonly struct Location(string filePath, int? line, int? column) { public string SourceFile { get; } = filePath; public int? Line { get; } = line; public int? Column { get; } = column; + public bool InDMStandard { get; } = inDMStandard; public override string ToString() { var builder = new StringBuilder(SourceFile ?? ""); diff --git a/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs b/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs index 1e016f410f..7b5b3631a0 100644 --- a/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs +++ b/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs @@ -8,7 +8,7 @@ namespace DMCompiler.Optimizer; * Provides a wrapper about BinaryWriter that stores information about the bytecode * for optimization and debugging. */ -internal class AnnotatedByteCodeWriter { +internal class AnnotatedByteCodeWriter(DMCompiler compiler) { private readonly List _annotatedBytecode = new(250); // 1/6th of max size for bytecode in tgstation @@ -34,7 +34,7 @@ public List GetAnnotatedBytecode() { public void WriteOpcode(DreamProcOpcode opcode, Location location) { _location = location; if (_requiredArgs.Count > 0) { - DMCompiler.ForcedError(location, "Expected argument"); + compiler.ForcedError(location, "Expected argument"); } var metadata = OpcodeMetadataCache.GetMetadata(opcode); @@ -62,11 +62,11 @@ public void WriteOpcode(DreamProcOpcode opcode, Location location) { public void WriteFloat(float val, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.Float) { - DMCompiler.ForcedError(location, "Expected floating argument"); + compiler.ForcedError(location, "Expected floating argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeFloat(val, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeFloat(val, location)); } /// @@ -77,11 +77,11 @@ public void WriteFloat(float val, Location location) { public void WriteArgumentType(DMCallArgumentsType argType, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.ArgType) { - DMCompiler.ForcedError(location, "Expected argument type argument"); + compiler.ForcedError(location, "Expected argument type argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeArgumentType(argType, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeArgumentType(argType, location)); } /// @@ -92,11 +92,11 @@ public void WriteArgumentType(DMCallArgumentsType argType, Location location) { public void WriteStackDelta(int delta, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.StackDelta) { - DMCompiler.ForcedError(location, "Expected stack delta argument"); + compiler.ForcedError(location, "Expected stack delta argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeStackDelta(delta, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeStackDelta(delta, location)); } /// @@ -106,12 +106,12 @@ public void WriteStackDelta(int delta, Location location) { /// The location of the type in the source code public void WriteType(DMValueType type, Location location) { if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.TypeId) { - DMCompiler.ForcedError(location, "Expected type argument"); + compiler.ForcedError(location, "Expected type argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeType(type, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeType(type, location)); } /// @@ -122,12 +122,12 @@ public void WriteType(DMValueType type, Location location) { public void WriteString(string value, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.String) { - DMCompiler.ForcedError(location, "Expected string argument"); + compiler.ForcedError(location, "Expected string argument"); } _requiredArgs.Pop(); - int stringId = DMObjectTree.AddString(value); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeString(stringId, location)); + int stringId = compiler.DMObjectTree.AddString(value); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeString(stringId, location)); } /// @@ -142,12 +142,12 @@ public void WriteFilterId(int filterTypeId, DreamPath filterPath, Location locat _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.FilterId) { - DMCompiler.ForcedError(location, "Expected filter argument"); + compiler.ForcedError(location, "Expected filter argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeFilter(filterTypeId, filterPath, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeFilter(filterTypeId, filterPath, location)); } /// @@ -158,15 +158,15 @@ public void WriteFilterId(int filterTypeId, DreamPath filterPath, Location locat public void WriteListSize(int value, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.ListSize) { - DMCompiler.ForcedError(location, "Expected list size argument"); + compiler.ForcedError(location, "Expected list size argument"); } if (value < 0) { - DMCompiler.ForcedError(location, "List size cannot be negative"); + compiler.ForcedError(location, "List size cannot be negative"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeListSize(value, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeListSize(value, location)); } /// @@ -177,10 +177,10 @@ public void WriteListSize(int value, Location location) { public void WriteLabel(string s, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Pop() != OpcodeArgType.Label) { - DMCompiler.ForcedError(location, "Expected label argument"); + compiler.ForcedError(location, "Expected label argument"); } - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeLabel(s, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeLabel(s, location)); _unresolvedLabelsInAnnotatedBytecode.Add((_annotatedBytecode.Count - 1, s)); } @@ -191,7 +191,7 @@ public void ResolveCodeLabelReferences(Stack pendingL // Failed to find the label in the given context if (label == null) { - DMCompiler.Emit( + compiler.Emit( WarningCode.ItemDoesntExist, reference.Location, $"Label \"{reference.Identifier}\" unreachable from scope or does not exist" @@ -199,7 +199,7 @@ public void ResolveCodeLabelReferences(Stack pendingL // Not cleaning away the placeholder will emit another compiler error // let's not do that _unresolvedLabelsInAnnotatedBytecode.RemoveAt( - _unresolvedLabelsInAnnotatedBytecode.FindIndex(((long Position, string LabelName) o) => + _unresolvedLabelsInAnnotatedBytecode.FindIndex(o => o.LabelName == reference.Placeholder) ); continue; @@ -239,7 +239,7 @@ public void ResizeStack(int sizeDelta) { _maxStackSize = Math.Max(_currentStackSize, _maxStackSize); if (_currentStackSize < 0 && !_negativeStackSizeError) { _negativeStackSizeError = true; - DMCompiler.ForcedError(_location, "Negative stack size"); + compiler.ForcedError(_location, "Negative stack size"); } } @@ -253,109 +253,109 @@ public int GetMaxStackSize() { public void WriteResource(string value, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.Resource) { - DMCompiler.ForcedError(location, "Expected resource argument"); + compiler.ForcedError(location, "Expected resource argument"); } _requiredArgs.Pop(); - int stringId = DMObjectTree.AddString(value); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeResource(stringId, location)); + int stringId = compiler.DMObjectTree.AddString(value); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeResource(stringId, location)); } public void WriteTypeId(int typeId, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.TypeId) { - DMCompiler.ForcedError(location, "Expected TypeID argument"); + compiler.ForcedError(location, "Expected TypeID argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeTypeId(typeId, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeTypeId(typeId, location)); } public void WriteProcId(int procId, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.ProcId) { - DMCompiler.ForcedError(location, "Expected ProcID argument"); + compiler.ForcedError(location, "Expected ProcID argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeProcId(procId, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeProcId(procId, location)); } public void WriteEnumeratorId(int enumeratorId, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.EnumeratorId) { - DMCompiler.ForcedError(location, "Expected EnumeratorID argument"); + compiler.ForcedError(location, "Expected EnumeratorID argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeEnumeratorId(enumeratorId, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeEnumeratorId(enumeratorId, location)); } public void WriteFormatCount(int formatCount, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.FormatCount) { - DMCompiler.ForcedError(location, "Expected format count argument"); + compiler.ForcedError(location, "Expected format count argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeFormatCount(formatCount, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeFormatCount(formatCount, location)); } public void WritePickCount(int count, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.PickCount) { - DMCompiler.ForcedError(location, "Expected pick count argument"); + compiler.ForcedError(location, "Expected pick count argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodePickCount(count, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodePickCount(count, location)); } public void WriteConcatCount(int count, Location location) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.ConcatCount) { - DMCompiler.ForcedError(location, "Expected concat count argument"); + compiler.ForcedError(location, "Expected concat count argument"); } _requiredArgs.Pop(); - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeConcatCount(count, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeConcatCount(count, location)); } public void WriteReference(DMReference reference, Location location, bool affectStack = true) { _location = location; if (_requiredArgs.Count == 0 || _requiredArgs.Pop() != OpcodeArgType.Reference) { - DMCompiler.ForcedError(location, "Expected reference argument"); + compiler.ForcedError(location, "Expected reference argument"); } switch (reference.RefType) { case DMReference.Type.Argument: case DMReference.Type.Local: _annotatedBytecode[^1] - .AddArg(new AnnotatedBytecodeReference(reference.RefType, reference.Index, location)); + .AddArg(compiler, new AnnotatedBytecodeReference(reference.RefType, reference.Index, location)); break; case DMReference.Type.Global: case DMReference.Type.GlobalProc: _annotatedBytecode[^1] - .AddArg(new AnnotatedBytecodeReference(reference.RefType, reference.Index, location)); + .AddArg(compiler, new AnnotatedBytecodeReference(reference.RefType, reference.Index, location)); break; case DMReference.Type.Field: - int fieldId = DMObjectTree.AddString(reference.Name); + int fieldId = compiler.DMObjectTree.AddString(reference.Name); _annotatedBytecode[^1] - .AddArg(new AnnotatedBytecodeReference(reference.RefType, fieldId, location)); + .AddArg(compiler, new AnnotatedBytecodeReference(reference.RefType, fieldId, location)); ResizeStack(affectStack ? -1 : 0); break; case DMReference.Type.SrcProc: case DMReference.Type.SrcField: - fieldId = DMObjectTree.AddString(reference.Name); + fieldId = compiler.DMObjectTree.AddString(reference.Name); _annotatedBytecode[^1] - .AddArg(new AnnotatedBytecodeReference(reference.RefType, fieldId, location)); + .AddArg(compiler, new AnnotatedBytecodeReference(reference.RefType, fieldId, location)); break; case DMReference.Type.ListIndex: - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeReference(reference.RefType, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeReference(reference.RefType, location)); ResizeStack(affectStack ? -2 : 0); break; @@ -363,13 +363,14 @@ public void WriteReference(DMReference reference, Location location, bool affect case DMReference.Type.Src: case DMReference.Type.Self: case DMReference.Type.Args: + case DMReference.Type.World: case DMReference.Type.Usr: case DMReference.Type.Invalid: - _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeReference(reference.RefType, location)); + _annotatedBytecode[^1].AddArg(compiler, new AnnotatedBytecodeReference(reference.RefType, location)); break; default: - DMCompiler.ForcedError(_location, $"Encountered unknown reference type {reference.RefType}"); + compiler.ForcedError(_location, $"Encountered unknown reference type {reference.RefType}"); break; } } diff --git a/DMCompiler/Optimizer/AnnotatedBytecode.cs b/DMCompiler/Optimizer/AnnotatedBytecode.cs index b84a212c93..3bb38adb09 100644 --- a/DMCompiler/Optimizer/AnnotatedBytecode.cs +++ b/DMCompiler/Optimizer/AnnotatedBytecode.cs @@ -1,10 +1,11 @@ +using System.Diagnostics.Contracts; using DMCompiler.Bytecode; using DMCompiler.DM; namespace DMCompiler.Optimizer; -public interface IAnnotatedBytecode { - public void AddArg(IAnnotatedBytecode arg); +internal interface IAnnotatedBytecode { + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg); void SetLocation(IAnnotatedBytecode location); void SetLocation(Location location); public Location GetLocation(); @@ -107,7 +108,7 @@ private bool MatchArgs(OpcodeArgType requiredArg, IAnnotatedBytecode arg) { } } - public void AddArg(IAnnotatedBytecode arg) { + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { _args.Add(arg); } @@ -156,8 +157,8 @@ public AnnotatedBytecodeVariable(int popOff, Location location) { Location = location; } - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a variable"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a variable"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -173,17 +174,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeInteger : IAnnotatedBytecode { - public Location Location; - public int Value; - - public AnnotatedBytecodeInteger(int value, Location location) { - Value = value; - Location = location; - } +internal sealed class AnnotatedBytecodeInteger(int value, Location location) : IAnnotatedBytecode { + public Location Location = location; + public int Value = value; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to an integer"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to an integer"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -199,17 +195,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeFloat : IAnnotatedBytecode { - public Location Location; - public float Value; - - public AnnotatedBytecodeFloat(float value, Location location) { - Value = value; - Location = location; - } +internal sealed class AnnotatedBytecodeFloat(float value, Location location) : IAnnotatedBytecode { + public Location Location = location; + public float Value = value; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a float"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a float"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -225,17 +216,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeString : IAnnotatedBytecode { - public int Id; - public Location Location; - - public AnnotatedBytecodeString(int id, Location location) { - Id = id; - Location = location; - } +internal sealed class AnnotatedBytecodeString(int id, Location location) : IAnnotatedBytecode { + public int Id = id; + public Location Location = location; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a string"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a string"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -249,19 +235,18 @@ public void SetLocation(Location loc) { public Location GetLocation() { return Location; } -} - -internal sealed class AnnotatedBytecodeArgumentType : IAnnotatedBytecode { - public Location Location; - public DMCallArgumentsType Value; - public AnnotatedBytecodeArgumentType(DMCallArgumentsType value, Location location) { - Value = value; - Location = location; + public string ResolveString(DMCompiler compiler) { + return compiler.DMObjectTree.StringTable[Id]; } +} - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to an argument type"); +internal sealed class AnnotatedBytecodeArgumentType(DMCallArgumentsType value, Location location) : IAnnotatedBytecode { + public Location Location = location; + public DMCallArgumentsType Value = value; + + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to an argument type"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -277,17 +262,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeType : IAnnotatedBytecode { - public Location Location; - public DMValueType Value; - - public AnnotatedBytecodeType(DMValueType value, Location location) { - Value = value; - Location = location; - } +internal sealed class AnnotatedBytecodeType(DMValueType value, Location location) : IAnnotatedBytecode { + public Location Location = location; + public DMValueType Value = value; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a type"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a type"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -303,17 +283,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeTypeId : IAnnotatedBytecode { - public Location Location; - public int TypeId; - - public AnnotatedBytecodeTypeId(int typeId, Location location) { - TypeId = typeId; - Location = location; - } +internal sealed class AnnotatedBytecodeTypeId(int typeId, Location location) : IAnnotatedBytecode { + public Location Location = location; + public int TypeId = typeId; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a type"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a type"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -333,8 +308,8 @@ internal sealed class AnnotatedBytecodeProcId(int procId, Location location) : I public Location Location = location; public int ProcId = procId; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a type"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a type"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -354,8 +329,8 @@ internal sealed class AnnotatedBytecodeEnumeratorId(int enumeratorId, Location l public Location Location = location; public int EnumeratorId = enumeratorId; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a type"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a type"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -371,17 +346,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeFormatCount : IAnnotatedBytecode { - public int Count; - public Location Location; - - public AnnotatedBytecodeFormatCount(int count, Location location) { - Count = count; - Location = location; - } +internal sealed class AnnotatedBytecodeFormatCount(int count, Location location) : IAnnotatedBytecode { + public int Count = count; + public Location Location = location; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a format count"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a format count"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -397,17 +367,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeStackDelta : IAnnotatedBytecode { - public int Delta; - public Location Location; - - public AnnotatedBytecodeStackDelta(int delta, Location location) { - Delta = delta; - Location = location; - } +internal sealed class AnnotatedBytecodeStackDelta(int delta, Location location) : IAnnotatedBytecode { + public int Delta = delta; + public Location Location = location; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a stack delta"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a stack delta"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -423,17 +388,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeListSize : IAnnotatedBytecode { - public Location Location; - public int Size; - - public AnnotatedBytecodeListSize(int size, Location location) { - Size = size; - Location = location; - } +internal sealed class AnnotatedBytecodeListSize(int size, Location location) : IAnnotatedBytecode { + public Location Location = location; + public int Size = size; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a list size"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a list size"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -449,17 +409,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodePickCount : IAnnotatedBytecode { - public int Count; - public Location Location; - - public AnnotatedBytecodePickCount(int count, Location location) { - Count = count; - Location = location; - } +internal sealed class AnnotatedBytecodePickCount(int count, Location location) : IAnnotatedBytecode { + public int Count = count; + public Location Location = location; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a pick count"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a pick count"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -475,17 +430,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeConcatCount : IAnnotatedBytecode { - public int Count; - public Location Location; - - public AnnotatedBytecodeConcatCount(int count, Location location) { - Count = count; - Location = location; - } +internal sealed class AnnotatedBytecodeConcatCount(int count, Location location) : IAnnotatedBytecode { + public int Count = count; + public Location Location = location; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a concat count"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a concat count"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -501,17 +451,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeResource : IAnnotatedBytecode { - public Location Location; - public int ResourceId; - - public AnnotatedBytecodeResource(int rid, Location location) { - ResourceId = rid; - Location = location; - } +internal sealed class AnnotatedBytecodeResource(int rid, Location location) : IAnnotatedBytecode { + public Location Location = location; + public int ResourceId = rid; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a resource"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a resource"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -527,17 +472,12 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeLabel : IAnnotatedBytecode { - public string LabelName; - public Location Location; - - public AnnotatedBytecodeLabel(string labelName, Location location) { - LabelName = labelName; - Location = location; - } +internal sealed class AnnotatedBytecodeLabel(string labelName, Location location) : IAnnotatedBytecode { + public string LabelName = labelName; + public Location Location = location; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a label"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a label"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -553,19 +493,14 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeFilter : IAnnotatedBytecode { - public DreamPath FilterPath; - public int FilterTypeId; - public Location Location; - - public AnnotatedBytecodeFilter(int filterTypeId, DreamPath filterPath, Location location) { - FilterTypeId = filterTypeId; - FilterPath = filterPath; - Location = location; - } +internal sealed class AnnotatedBytecodeFilter(int filterTypeId, DreamPath filterPath, Location location) + : IAnnotatedBytecode { + public DreamPath FilterPath = filterPath; + public int FilterTypeId = filterTypeId; + public Location Location = location; - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a filter"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a filter"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -581,24 +516,17 @@ public Location GetLocation() { } } -internal sealed class AnnotatedBytecodeReference : IAnnotatedBytecode { - public int Index; - public Location Location; - public DMReference.Type RefType; - - public AnnotatedBytecodeReference(DMReference.Type refType, int index, Location location) { - RefType = refType; - Index = index; - Location = location; - } +internal sealed class AnnotatedBytecodeReference(DMReference.Type refType, int index, Location location) + : IAnnotatedBytecode { + public int Index = index; + public Location Location = location; + public DMReference.Type RefType = refType; - public AnnotatedBytecodeReference(DMReference.Type refType, Location location) { - RefType = refType; - Location = location; + public AnnotatedBytecodeReference(DMReference.Type refType, Location location) : this(refType, 0, location) { } - public void AddArg(IAnnotatedBytecode arg) { - DMCompiler.ForcedError(Location, "Cannot add args to a reference"); + public void AddArg(DMCompiler compiler, IAnnotatedBytecode arg) { + compiler.ForcedError(Location, "Cannot add args to a reference"); } public void SetLocation(IAnnotatedBytecode loc) { @@ -612,4 +540,9 @@ public void SetLocation(Location loc) { public Location GetLocation() { return Location; } + + [Pure] + public bool Equals(AnnotatedBytecodeReference other) { + return RefType == other.RefType && Index == other.Index; + } } diff --git a/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs b/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs index 0f343d97cc..497a31d558 100644 --- a/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs +++ b/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs @@ -1,14 +1,13 @@ using System.IO; using DMCompiler.Bytecode; using DMCompiler.Compiler; -using DMCompiler.DM; using DMCompiler.Json; namespace DMCompiler.Optimizer; -internal class AnnotatedBytecodeSerializer { +internal class AnnotatedBytecodeSerializer(DMCompiler compiler) { private readonly List _localVariables = new(); - private BinaryWriter _bytecodeWriter; + private BinaryWriter? _bytecodeWriter; private Dictionary _labels = new(); private List<(long Position, string LabelName)> _unresolvedLabels = new(); private int _lastFileId = -1; @@ -18,11 +17,8 @@ internal class AnnotatedBytecodeSerializer { public List SourceInfo = new(); - public AnnotatedBytecodeSerializer() { - _bytecodeWriter = new BinaryWriter(Bytecode); - } - public byte[]? Serialize(List annotatedBytecode) { + _bytecodeWriter ??= new BinaryWriter(Bytecode); foreach (IAnnotatedBytecode bytecodeChunk in annotatedBytecode) { if (bytecodeChunk is AnnotatedBytecodeInstruction instruction) { SerializeInstruction(instruction); @@ -58,8 +54,9 @@ public AnnotatedBytecodeSerializer() { } private void SerializeInstruction(AnnotatedBytecodeInstruction instruction) { + _bytecodeWriter ??= new BinaryWriter(Bytecode); if (instruction.Location.Line != null && (_location == null || instruction.Location.Line != _location?.Line)) { - int sourceFileId = DMObjectTree.AddString(instruction.Location.SourceFile); + int sourceFileId = compiler.DMObjectTree.AddString(instruction.Location.SourceFile); if (_lastFileId != sourceFileId) { _lastFileId = sourceFileId; SourceInfo.Add(new SourceInfoJson { @@ -149,12 +146,13 @@ private void SerializeLabel(AnnotatedBytecodeLabel label) { } private void ResolveLabels() { + _bytecodeWriter ??= new BinaryWriter(Bytecode); foreach ((long position, string labelName) in _unresolvedLabels) { if (_labels.TryGetValue(labelName, out int labelPosition)) { _bytecodeWriter.Seek((int)position, SeekOrigin.Begin); _bytecodeWriter.Write((int)labelPosition); } else { - DMCompiler.Emit(WarningCode.BadLabel, Location.Internal, + compiler.Emit(WarningCode.BadLabel, Location.Internal, "Label \"" + labelName + "\" could not be resolved"); } } @@ -164,6 +162,7 @@ private void ResolveLabels() { } public void WriteReference(AnnotatedBytecodeReference reference) { + _bytecodeWriter ??= new BinaryWriter(Bytecode); _bytecodeWriter.Write((byte)reference.RefType); switch (reference.RefType) { case DMReference.Type.Argument: @@ -190,12 +189,13 @@ public void WriteReference(AnnotatedBytecodeReference reference) { case DMReference.Type.Src: case DMReference.Type.Self: case DMReference.Type.Args: + case DMReference.Type.World: case DMReference.Type.Usr: case DMReference.Type.Invalid: break; default: - DMCompiler.ForcedError(_location ?? Location.Unknown, $"Encountered unknown reference type {reference.RefType}"); + compiler.ForcedError(_location ?? Location.Unknown, $"Encountered unknown reference type {reference.RefType}"); break; } } diff --git a/DMCompiler/Optimizer/BytecodeOptimizer.cs b/DMCompiler/Optimizer/BytecodeOptimizer.cs index 8b1eca71d0..89389db81b 100644 --- a/DMCompiler/Optimizer/BytecodeOptimizer.cs +++ b/DMCompiler/Optimizer/BytecodeOptimizer.cs @@ -3,16 +3,15 @@ namespace DMCompiler.Optimizer; public class BytecodeOptimizer { - public List Optimize(List input) { - if (input.Count == 0) { - return input; - } + internal void Optimize(DMCompiler compiler, List input) { + if (input.Count == 0) + return; RemoveUnreferencedLabels(input); JoinAndForwardLabels(input); RemoveUnreferencedLabels(input); - PeepholeOptimizer.RunPeephole(input); - return input; + + PeepholeOptimizer.RunPeephole(compiler, input); } private static void RemoveUnreferencedLabels(List input) { diff --git a/DMCompiler/Optimizer/CompactorOptimizations.cs b/DMCompiler/Optimizer/CompactorOptimizations.cs new file mode 100644 index 0000000000..9a6525e2fa --- /dev/null +++ b/DMCompiler/Optimizer/CompactorOptimizations.cs @@ -0,0 +1,378 @@ +using DMCompiler.Bytecode; + +// ReSharper disable UnusedType.Global + +namespace DMCompiler.Optimizer; + +#region BytecodeCompactors + +// PushString [string] +// ... +// PushString [string] +// -> PushNStrings [count] [string] ... [string] +internal sealed class PushNStrings : IOptimization { + public OptPass OptimizationPass => OptPass.BytecodeCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushString, + DreamProcOpcode.PushString + ]; + } + + public void Apply(DMCompiler compiler, List input, int index) { + int count = 0; + int stackDelta = 0; + + while (index + count < input.Count && + input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushString }) { + count++; + } + + List args = new List(count + 1) { new AnnotatedBytecodeInteger(count, new Location()) }; + + for (int i = 0; i < count; i++) { + AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]); + args.Add(instruction.GetArg(0)); + stackDelta++; + } + + input.RemoveRange(index, count); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNStrings, stackDelta, args)); + } +} + +// PushFloat [float] +// ... +// PushFloat [float] +// -> PushNFloats [count] [float] ... [float] +internal sealed class PushNFloats : IOptimization { + public OptPass OptimizationPass => OptPass.BytecodeCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushFloat, + DreamProcOpcode.PushFloat + ]; + } + + public void Apply(DMCompiler compiler, List input, int index) { + int count = 0; + int stackDelta = 0; + + while (index + count < input.Count && + input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushFloat }) { + count++; + } + + List args = new List(count + 1) { new AnnotatedBytecodeInteger(count, new Location()) }; + + for (int i = 0; i < count; i++) { + AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]); + args.Add(instruction.GetArg(0)); + stackDelta++; + } + + input.RemoveRange(index, count); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNFloats, stackDelta, args)); + } +} + +// PushReferenceValue [ref] +// ... +// PushReferenceValue [ref] +// -> PushNRef [count] [ref] ... [ref] +internal sealed class PushNRef : IOptimization { + public OptPass OptimizationPass => OptPass.BytecodeCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushReferenceValue, + DreamProcOpcode.PushReferenceValue + ]; + } + + public void Apply(DMCompiler compiler, List input, int index) { + int count = 0; + int stackDelta = 0; + + while (index + count < input.Count && + input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushReferenceValue }) { + count++; + } + + List args = new List(count + 1) { new AnnotatedBytecodeInteger(count, new Location()) }; + + for (int i = 0; i < count; i++) { + AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]); + args.Add(instruction.GetArg(0)); + stackDelta++; + } + + input.RemoveRange(index, count); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNRefs, stackDelta, args)); + } +} + +// PushString [string] +// PushFloat [float] +// -> PushStringFloat [string] [float] +// or if there's multiple +// -> PushNOfStringFloat [count] [string] [float] ... [string] [float] +internal sealed class PushStringFloat : IOptimization { + public OptPass OptimizationPass => OptPass.BytecodeCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushString, + DreamProcOpcode.PushFloat + ]; + } + + public void Apply(DMCompiler compiler, List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + int count = 0; + while (index + count*2 + 1 < input.Count && + input[index + count * 2] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushString } && input[index + count * 2 + 1] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushFloat }) { + count++; + } + + // If the pattern only occurs once, replace with PushStringFloat and return + if (count == 1) { + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); + AnnotatedBytecodeString pushVal1 = firstInstruction.GetArg(0); + AnnotatedBytecodeFloat pushVal2 = secondInstruction.GetArg(0); + + input.RemoveRange(index, 2); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushStringFloat, [pushVal1, pushVal2])); + return; + } + + // Otherwise, replace with PushNOfStringFloat + + int stackDelta = 0; + List args = new List(2 * count + 1) { new AnnotatedBytecodeInteger(count, input[index].GetLocation()) }; + + for (int i = 0; i < count; i++) { + AnnotatedBytecodeInstruction stringInstruction = (AnnotatedBytecodeInstruction)(input[index + i*2]); + AnnotatedBytecodeInstruction floatInstruction = (AnnotatedBytecodeInstruction)(input[index + i*2 + 1]); + args.Add(stringInstruction.GetArg(0)); + args.Add(floatInstruction.GetArg(0)); + stackDelta += 2; + } + + input.RemoveRange(index, count * 2); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNOfStringFloats, stackDelta, args)); + } +} + +// PushResource [resource] +// ... +// PushResource [resource] +// -> PushNResources [count] [resource] ... [resource] +internal sealed class PushNResources : IOptimization { + public OptPass OptimizationPass => OptPass.BytecodeCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushResource, + DreamProcOpcode.PushResource + ]; + } + + public void Apply(DMCompiler compiler, List input, int index) { + int count = 0; + int stackDelta = 0; + while (index + count < input.Count && + input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushResource }) { + count++; + } + + List args = new List(count + 1) { new AnnotatedBytecodeInteger(count, new Location()) }; + + for (int i = 0; i < count; i++) { + AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]); + args.Add(instruction.GetArg(0)); + stackDelta++; + } + + input.RemoveRange(index, count); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNResources, stackDelta, args)); + } +} + +#endregion + +#region ListCompactors + +// PushNFloats [count] [float] ... [float] +// CreateList [count] +// -> CreateListNFloats [count] [float] ... [float] +internal sealed class CreateListNFloats : IOptimization { + public OptPass OptimizationPass => OptPass.ListCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushNFloats, + DreamProcOpcode.CreateList + ]; + } + + public bool CheckPreconditions(List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); + int pushVal1 = firstInstruction.GetArg(0).Value; + int pushVal2 = secondInstruction.GetArg(0).Size; + + return pushVal1 == pushVal2; + } + + public void Apply(DMCompiler compiler, List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + int pushVal1 = firstInstruction.GetArg(0).Value; + + List args = new List(pushVal1 + 1) { new AnnotatedBytecodeInteger(pushVal1, new Location()) }; + args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]); + + input.RemoveRange(index, 2); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNFloats, 1, args)); + } +} + +// PushNStrings [count] [string] ... [string] +// CreateList [count] +// -> CreateListNStrings [count] [string] ... [string] +internal sealed class CreateListNStrings : IOptimization { + public OptPass OptimizationPass => OptPass.ListCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushNStrings, + DreamProcOpcode.CreateList + ]; + } + + public bool CheckPreconditions(List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); + int pushVal1 = firstInstruction.GetArg(0).Value; + int pushVal2 = secondInstruction.GetArg(0).Size; + + return pushVal1 == pushVal2; + } + + public void Apply(DMCompiler compiler, List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + int pushVal1 = firstInstruction.GetArg(0).Value; + + List args = new List(pushVal1 + 1) { new AnnotatedBytecodeInteger(pushVal1, new Location()) }; + args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]); + + input.RemoveRange(index, 2); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNStrings, 1, args)); + } +} + +// PushNResources [count] [resource] ... [resource] +// CreateList [count] +// -> CreateListNResources [count] [resource] ... [resource] +internal sealed class CreateListNResources : IOptimization { + public OptPass OptimizationPass => OptPass.ListCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushNResources, + DreamProcOpcode.CreateList + ]; + } + + public bool CheckPreconditions(List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); + int pushVal1 = firstInstruction.GetArg(0).Value; + int pushVal2 = secondInstruction.GetArg(0).Size; + + return pushVal1 == pushVal2; + } + + public void Apply(DMCompiler compiler, List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + int pushVal1 = firstInstruction.GetArg(0).Value; + + List args = new List(pushVal1 + 1) { new AnnotatedBytecodeInteger(pushVal1, new Location()) }; + args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]); + + input.RemoveRange(index, 2); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNResources, 1, args)); + } +} + +// PushNRefs [count] [ref] ... [ref] +// CreateList [count] +// -> CreateListNRefs [count] [ref] ... [ref] +internal sealed class CreateListNRefs : IOptimization { + public OptPass OptimizationPass => OptPass.ListCompactor; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushNRefs, + DreamProcOpcode.CreateList + ]; + } + + public bool CheckPreconditions(List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index),"Bytecode index is outside the bounds of the input list."); + } + + int pushVal1 = ((AnnotatedBytecodeInstruction)input[index]).GetArg(0).Value; + int pushVal2 = ((AnnotatedBytecodeInstruction)input[index + 1]).GetArg(0).Size; + + return pushVal1 == pushVal2; + } + + public void Apply(DMCompiler compiler, List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Bytecode index is outside the bounds of the input list."); + } + + var firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + int pushVal1 = firstInstruction.GetArg(0).Value; + + List args = new List(1 + pushVal1) { new AnnotatedBytecodeInteger(pushVal1, new Location()) }; + args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]); + + input.RemoveRange(index, 2); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNRefs, 1, args)); + } +} + +#endregion diff --git a/DMCompiler/Optimizer/PeepholeOptimizations.cs b/DMCompiler/Optimizer/PeepholeOptimizations.cs index 6f5c04d2f7..56f6f4577f 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizations.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizations.cs @@ -1,11 +1,15 @@ using DMCompiler.Bytecode; +// ReSharper disable UnusedType.Global + namespace DMCompiler.Optimizer; // Append [ref] // Pop // -> AppendNoPush [ref] -internal sealed class AppendNoPush : IPeepholeOptimization { +internal sealed class AppendNoPush : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.Append, @@ -13,7 +17,7 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { if (index + 1 >= input.Count) { throw new ArgumentOutOfRangeException(nameof(index), "Bytecode index is outside the bounds of the input list."); } @@ -29,7 +33,9 @@ public void Apply(List input, int index) { // Assign [ref] // Pop // -> AssignNoPush [ref] -internal sealed class AssignNoPush : IPeepholeOptimization { +internal sealed class AssignNoPush : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.Assign, @@ -37,7 +43,7 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { if (index + 1 >= input.Count) { throw new ArgumentOutOfRangeException(nameof(index), "Bytecode index is outside the bounds of the input list."); } @@ -53,7 +59,9 @@ public void Apply(List input, int index) { // PushNull // AssignNoPush [ref] // -> AssignNull [ref] -internal sealed class AssignNull : IPeepholeOptimization { +internal sealed class AssignNull : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushNull, @@ -61,7 +69,7 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { // Ensure that we have at least two elements from the starting index to avoid out-of-bound errors if (index + 1 >= input.Count) { throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); @@ -82,7 +90,9 @@ public void Apply(List input, int index) { // PushReferenceValue [ref] // DereferenceField [field] // -> PushRefAndDereferenceField [ref, field] -internal sealed class PushField : IPeepholeOptimization { +internal sealed class PushField : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushReferenceValue, @@ -90,7 +100,7 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { if (index + 1 >= input.Count) { throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); } @@ -109,7 +119,9 @@ public void Apply(List input, int index) { // PushReferenceValue [ref] // Return // -> ReturnReferenceValue [ref] -internal class ReturnReferenceValue : IPeepholeOptimization { +internal class ReturnReferenceValue : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushReferenceValue, @@ -117,7 +129,7 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); AnnotatedBytecodeReference pushVal = firstInstruction.GetArg(0); input.RemoveRange(index, 2); @@ -128,7 +140,9 @@ public void Apply(List input, int index) { // PushFloat [float] // Return // -> ReturnFloat [float] -internal class ReturnFloat : IPeepholeOptimization { +internal class ReturnFloat : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -136,9 +150,9 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal); - IPeepholeOptimization.ReplaceInstructions(input, index, 2, + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal); + IOptimization.ReplaceInstructions(input, index, 2, new AnnotatedBytecodeInstruction(DreamProcOpcode.ReturnFloat, [new AnnotatedBytecodeFloat(pushVal, firstInstruction.Location)])); } } @@ -146,7 +160,9 @@ public void Apply(List input, int index) { // PushReferenceValue [ref] // JumpIfFalse [label] // -> JumpIfReferenceFalse [ref] [label] -internal sealed class JumpIfReferenceFalse : IPeepholeOptimization { +internal sealed class JumpIfReferenceFalse : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushReferenceValue, @@ -154,7 +170,7 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { if (index + 1 >= input.Count) { throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); } @@ -170,169 +186,12 @@ public void Apply(List input, int index) { } } -// PushString [string] -// ... -// PushString [string] -// -> PushNStrings [count] [string] ... [string] -internal sealed class PushNStrings : IPeepholeOptimization { - public ReadOnlySpan GetOpcodes() { - return [ - DreamProcOpcode.PushString, - DreamProcOpcode.PushString - ]; - } - - public void Apply(List input, int index) { - int count = 0; - int stackDelta = 0; - - while (index + count < input.Count && - input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushString }) { - count++; - } - - List args = new List(count + 1); - args.Add(new AnnotatedBytecodeInteger(count, new Location())); - - for (int i = 0; i < count; i++) { - AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]); - args.Add(instruction.GetArg(0)); - stackDelta++; - } - - input.RemoveRange(index, count); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNStrings, stackDelta, args)); - } -} - -// PushFloat [float] -// ... -// PushFloat [float] -// -> PushNFloats [count] [float] ... [float] -internal sealed class PushNFloats : IPeepholeOptimization { - public ReadOnlySpan GetOpcodes() { - return [ - DreamProcOpcode.PushFloat, - DreamProcOpcode.PushFloat - ]; - } - - public void Apply(List input, int index) { - int count = 0; - int stackDelta = 0; - - while (index + count < input.Count && - input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushFloat }) { - count++; - } - - List args = new List(count + 1); - args.Add(new AnnotatedBytecodeInteger(count, new Location())); - - for (int i = 0; i < count; i++) { - AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]); - args.Add(instruction.GetArg(0)); - stackDelta++; - } - - input.RemoveRange(index, count); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNFloats, stackDelta, args)); - } -} - -// PushReferenceValue [ref] -// ... -// PushReferenceValue [ref] -// -> PushNRef [count] [ref] ... [ref] -internal sealed class PushNRef : IPeepholeOptimization { - public ReadOnlySpan GetOpcodes() { - return [ - DreamProcOpcode.PushReferenceValue, - DreamProcOpcode.PushReferenceValue - ]; - } - - public void Apply(List input, int index) { - int count = 0; - int stackDelta = 0; - - while (index + count < input.Count && - input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushReferenceValue }) { - count++; - } - - List args = new List(count + 1); - args.Add(new AnnotatedBytecodeInteger(count, new Location())); - - for (int i = 0; i < count; i++) { - AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]); - args.Add(instruction.GetArg(0)); - stackDelta++; - } - - input.RemoveRange(index, count); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNRefs, stackDelta, args)); - } -} - -// PushString [string] -// PushFloat [float] -// -> PushStringFloat [string] [float] -// or if there's multiple -// -> PushNOfStringFloat [count] [string] [float] ... [string] [float] -internal sealed class PushStringFloat : IPeepholeOptimization { - public ReadOnlySpan GetOpcodes() { - return [ - DreamProcOpcode.PushString, - DreamProcOpcode.PushFloat - ]; - } - - public void Apply(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); - } - - int count = 0; - while (index + count*2 + 1 < input.Count && - input[index + count * 2] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushString } && input[index + count * 2 + 1] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushFloat }) { - count++; - } - - // If the pattern only occurs once, replace with PushStringFloat and return - if (count == 1) { - AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); - AnnotatedBytecodeString pushVal1 = firstInstruction.GetArg(0); - AnnotatedBytecodeFloat pushVal2 = secondInstruction.GetArg(0); - - input.RemoveRange(index, 2); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushStringFloat, [pushVal1, pushVal2])); - return; - } - - // Otherwise, replace with PushNOfStringFloat - - int stackDelta = 0; - List args = new List(2 * count + 1) { new AnnotatedBytecodeInteger(count, input[index].GetLocation()) }; - - for (int i = 0; i < count; i++) { - AnnotatedBytecodeInstruction stringInstruction = (AnnotatedBytecodeInstruction)(input[index + i*2]); - AnnotatedBytecodeInstruction floatInstruction = (AnnotatedBytecodeInstruction)(input[index + i*2 + 1]); - args.Add(stringInstruction.GetArg(0)); - args.Add(floatInstruction.GetArg(0)); - stackDelta += 2; - } - - input.RemoveRange(index, count * 2); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNOfStringFloats, stackDelta, args)); - } -} - // PushFloat [float] // SwitchCase [label] // -> SwitchOnFloat [float] [label] -internal sealed class SwitchOnFloat : IPeepholeOptimization { +internal sealed class SwitchOnFloat : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -340,7 +199,7 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { if (index + 1 >= input.Count) { throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); } @@ -358,7 +217,9 @@ public void Apply(List input, int index) { // PushString [string] // SwitchCase [label] // -> SwitchOnString [string] [label] -internal sealed class SwitchOnString : IPeepholeOptimization { +internal sealed class SwitchOnString : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushString, @@ -366,7 +227,7 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { if (index + 1 >= input.Count) { throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); } @@ -381,248 +242,136 @@ public void Apply(List input, int index) { } } -// PushResource [resource] -// ... -// PushResource [resource] -// -> PushNResources [count] [resource] ... [resource] -internal sealed class PushNResources : IPeepholeOptimization { +// Jump [label1] +// Jump [label2] <- Dead code +// -> Jump [label1] +internal sealed class RemoveJumpFollowedByJump : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ - DreamProcOpcode.PushResource, - DreamProcOpcode.PushResource + DreamProcOpcode.Jump, + DreamProcOpcode.Jump ]; } - public void Apply(List input, int index) { - int count = 0; - int stackDelta = 0; - while (index + count < input.Count && - input[index + count] is AnnotatedBytecodeInstruction { Opcode: DreamProcOpcode.PushResource }) { - count++; - } - - List args = new List(count + 1); - args.Add(new AnnotatedBytecodeInteger(count, new Location())); - - for (int i = 0; i < count; i++) { - AnnotatedBytecodeInstruction instruction = (AnnotatedBytecodeInstruction)(input[index + i]); - args.Add(instruction.GetArg(0)); - stackDelta++; - } - - input.RemoveRange(index, count); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushNResources, stackDelta, args)); + public void Apply(DMCompiler compiler, List input, int index) { + input.RemoveAt(index + 1); } } -// PushNFloats [count] [float] ... [float] -// CreateList [count] -// -> CreateListNFloats [count] [float] ... [float] -internal sealed class CreateListNFloats : IPeepholeOptimization { +// PushType [type] +// IsType +// -> IsTypeDirect [type] +internal sealed class IsTypeDirect : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ - DreamProcOpcode.PushNFloats, - DreamProcOpcode.CreateList + DreamProcOpcode.PushType, + DreamProcOpcode.IsType ]; } - public bool CheckPreconditions(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); - } - - AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); - int pushVal1 = firstInstruction.GetArg(0).Value; - int pushVal2 = secondInstruction.GetArg(0).Size; - - return pushVal1 == pushVal2; - } - - public void Apply(List input, int index) { + public void Apply(DMCompiler compiler, List input, int index) { if (index + 1 >= input.Count) { throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); } - AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - int pushVal1 = firstInstruction.GetArg(0).Value; - - List args = new List(pushVal1 + 1); - args.Add(new AnnotatedBytecodeInteger(pushVal1, new Location())); - args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]); + var firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeTypeId pushVal = firstInstruction.GetArg(0); input.RemoveRange(index, 2); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNFloats, 1, args)); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.IsTypeDirect, [pushVal])); } } -// PushNStrings [count] [string] ... [string] -// CreateList [count] -// -> CreateListNStrings [count] [string] ... [string] -internal sealed class CreateListNStrings : IPeepholeOptimization { - public ReadOnlySpan GetOpcodes() { - return [ - DreamProcOpcode.PushNStrings, - DreamProcOpcode.CreateList - ]; - } - - public bool CheckPreconditions(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); - } - - AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); - int pushVal1 = firstInstruction.GetArg(0).Value; - int pushVal2 = secondInstruction.GetArg(0).Size; - - return pushVal1 == pushVal2; - } - - public void Apply(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); - } - - AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - int pushVal1 = firstInstruction.GetArg(0).Value; - - List args = new List(pushVal1 + 1); - args.Add(new AnnotatedBytecodeInteger(pushVal1, new Location())); - args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]); +#region Constant Folding - input.RemoveRange(index, 2); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNStrings, 1, args)); - } -} +// PushFloat [constant] +// BitNot +// -> PushFloat [result] +internal sealed class ConstFoldBitNot : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; -// PushNResources [count] [resource] ... [resource] -// CreateList [count] -// -> CreateListNResources [count] [resource] ... [resource] -internal sealed class CreateListNResources : IPeepholeOptimization { public ReadOnlySpan GetOpcodes() { return [ - DreamProcOpcode.PushNResources, - DreamProcOpcode.CreateList + DreamProcOpcode.PushFloat, + DreamProcOpcode.BitNot ]; } - public bool CheckPreconditions(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); - } - - AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); - int pushVal1 = firstInstruction.GetArg(0).Value; - int pushVal2 = secondInstruction.GetArg(0).Size; + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - return pushVal1 == pushVal2; - } + var args = new List(1) {new AnnotatedBytecodeFloat(((~(int)pushVal1) & 0xFFFFFF), firstInstruction.Location)}; - public void Apply(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); - } - - AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - int pushVal1 = firstInstruction.GetArg(0).Value; - - List args = new List(pushVal1 + 1); - args.Add(new AnnotatedBytecodeInteger(pushVal1, new Location())); - args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]); - - input.RemoveRange(index, 2); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNResources, 1, args)); + IOptimization.ReplaceInstructions(input, index, 2, + new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } -// PushNRefs [count] [ref] ... [ref] -// CreateList [count] -// -> CreateListNRefs [count] [ref] ... [ref] -internal sealed class CreateListNRefs : IPeepholeOptimization { +// PushFloat [constant] +// PushFloat [constant] +// BitOr +// -> PushFloat [result] +internal sealed class ConstFoldBitOr : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ - DreamProcOpcode.PushNRefs, - DreamProcOpcode.CreateList + DreamProcOpcode.PushFloat, + DreamProcOpcode.PushFloat, + DreamProcOpcode.BitOr, ]; } - public bool CheckPreconditions(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index),"Bytecode index is outside the bounds of the input list."); - } - - int pushVal1 = ((AnnotatedBytecodeInstruction)input[index]).GetArg(0).Value; - int pushVal2 = ((AnnotatedBytecodeInstruction)input[index + 1]).GetArg(0).Size; - - return pushVal1 == pushVal2; - } - - public void Apply(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index), "Bytecode index is outside the bounds of the input list."); - } + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - var firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - int pushVal1 = firstInstruction.GetArg(0).Value; + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); - List args = new List(1 + pushVal1); - args.Add(new AnnotatedBytecodeInteger(pushVal1, new Location())); - args.AddRange(firstInstruction.GetArgs()[1..(pushVal1+1)]); + var args = new List(1) {new AnnotatedBytecodeFloat(((int)pushVal1 | (int)pushVal2), firstInstruction.Location)}; - input.RemoveRange(index, 2); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.CreateListNRefs, 1, args)); + IOptimization.ReplaceInstructions(input, index, 3, + new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } -// Jump [label1] -// Jump [label2] <- Dead code -// -> Jump [label1] -internal sealed class RemoveJumpFollowedByJump : IPeepholeOptimization { - public ReadOnlySpan GetOpcodes() { - return [ - DreamProcOpcode.Jump, - DreamProcOpcode.Jump - ]; - } - - public void Apply(List input, int index) { - input.RemoveAt(index + 1); - } -} +// PushFloat [constant] +// PushFloat [constant] +// BitAnd +// -> PushFloat [result] +internal sealed class ConstFoldBitAnd : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; -// PushType [type] -// IsType -// -> IsTypeDirect [type] -internal sealed class IsTypeDirect : IPeepholeOptimization { public ReadOnlySpan GetOpcodes() { return [ - DreamProcOpcode.PushType, - DreamProcOpcode.IsType + DreamProcOpcode.PushFloat, + DreamProcOpcode.PushFloat, + DreamProcOpcode.BitAnd, ]; } - public void Apply(List input, int index) { - if (index + 1 >= input.Count) { - throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); - } + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - var firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); - AnnotatedBytecodeTypeId pushVal = firstInstruction.GetArg(0); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); - input.RemoveRange(index, 2); - input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.IsTypeDirect, [pushVal])); + var args = new List(1) {new AnnotatedBytecodeFloat(((int)pushVal1 & (int)pushVal2), firstInstruction.Location)}; + + IOptimization.ReplaceInstructions(input, index, 3, + new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } -#region Constant Folding // PushFloat [constant] // PushFloat [constant] // Multiply // -> PushFloat [result] -internal sealed class ConstFoldMultiply : IPeepholeOptimization { +internal sealed class ConstFoldMultiply : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -631,14 +380,14 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); var args = new List(1) {new AnnotatedBytecodeFloat(pushVal1 * pushVal2, firstInstruction.Location)}; - IPeepholeOptimization.ReplaceInstructions(input, index, 3, + IOptimization.ReplaceInstructions(input, index, 3, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } @@ -647,7 +396,9 @@ public void Apply(List input, int index) { // PushFloat [constant] // Divide // -> PushFloat [result] -internal sealed class ConstFoldDivide : IPeepholeOptimization { +internal sealed class ConstFoldDivide : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -656,16 +407,16 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); // At runtime, given "A / B" we pop B then A // In the peephole optimizer, index is "A", index+1 is "B" var args = new List(1) {new AnnotatedBytecodeFloat(pushVal1 / pushVal2, firstInstruction.Location)}; - IPeepholeOptimization.ReplaceInstructions(input, index, 3, + IOptimization.ReplaceInstructions(input, index, 3, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } @@ -674,7 +425,9 @@ public void Apply(List input, int index) { // PushFloat [constant] // Add // -> PushFloat [result] -internal sealed class ConstFoldAdd : IPeepholeOptimization { +internal sealed class ConstFoldAdd : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -683,23 +436,54 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); var args = new List(1) {new AnnotatedBytecodeFloat(pushVal1 + pushVal2, firstInstruction.Location)}; - IPeepholeOptimization.ReplaceInstructions(input, index, 3, + IOptimization.ReplaceInstructions(input, index, 3, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } +// PushString [constant] +// PushString [constant] +// Add +// -> PushString [result] +internal sealed class ConstFoldAddStrings : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.PushString, + DreamProcOpcode.PushString, + DreamProcOpcode.Add, + ]; + } + + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = (AnnotatedBytecodeInstruction)input[index]; + var firstString = firstInstruction.GetArg(0); + var secondString = ((AnnotatedBytecodeInstruction)input[index+1]).GetArg(0); + + var combinedId = compiler.DMObjectTree.AddString(firstString.ResolveString(compiler) + secondString.ResolveString(compiler)); // TODO: Currently doesn't handle removing strings from the string tree that have no other references + + var args = new List(1) {new AnnotatedBytecodeString(combinedId, firstInstruction.Location)}; + + IOptimization.ReplaceInstructions(input, index, 3, + new AnnotatedBytecodeInstruction(DreamProcOpcode.PushString, 1, args)); + } +} + // PushFloat [constant] // PushFloat [constant] // Subtract // -> PushFloat [result] -internal sealed class ConstFoldSubtract : IPeepholeOptimization { +internal sealed class ConstFoldSubtract : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -708,16 +492,16 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); // At runtime, given "A - B" we pop B then A // In the peephole optimizer, index is "A", index+1 is "B" var args = new List(1) {new AnnotatedBytecodeFloat(pushVal1 - pushVal2, firstInstruction.Location)}; - IPeepholeOptimization.ReplaceInstructions(input, index, 3, + IOptimization.ReplaceInstructions(input, index, 3, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } @@ -726,7 +510,9 @@ public void Apply(List input, int index) { // PushFloat [constant] // Modulus // -> PushFloat [result] -internal sealed class ConstFoldModulus : IPeepholeOptimization { +internal sealed class ConstFoldModulus : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -735,16 +521,16 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); // At runtime, given "A % B" we pop B then A // In the peephole optimizer, index is "A", index+1 is "B" var args = new List(1) {new AnnotatedBytecodeFloat((int)pushVal1 % (int)pushVal2, firstInstruction.Location)}; - IPeepholeOptimization.ReplaceInstructions(input, index, 3, + IOptimization.ReplaceInstructions(input, index, 3, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } @@ -753,7 +539,9 @@ public void Apply(List input, int index) { // PushFloat [constant] // Power // -> PushFloat [result] -internal sealed class ConstFoldPower : IPeepholeOptimization { +internal sealed class ConstFoldPower : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -762,25 +550,111 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); - IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); // At runtime, given "A ** B" we pop B then A // In the peephole optimizer, index is "A", index+1 is "B" var args = new List(1) {new AnnotatedBytecodeFloat(MathF.Pow(pushVal1, pushVal2), firstInstruction.Location)}; - IPeepholeOptimization.ReplaceInstructions(input, index, 3, + IOptimization.ReplaceInstructions(input, index, 3, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } +// AssignNoPush [ref] +// PushReferenceValue [ref] +// -> Assign [ref] +// These opcodes can be reduced to a single Assign as long as the [ref]s are the same +internal sealed class AssignAndPushReferenceValue : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.AssignNoPush, + DreamProcOpcode.PushReferenceValue + ]; + } + + /// + /// We can only apply this optimization if both opcodes refer to the same reference + /// + public bool CheckPreconditions(List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); + + AnnotatedBytecodeReference assignTarget = firstInstruction.GetArg(0); + AnnotatedBytecodeReference pushTarget = secondInstruction.GetArg(0); + + return assignTarget.Equals(pushTarget); + } + + public void Apply(DMCompiler compiler, List input, int index) { + // We check the input bounds in CheckPreconditions, so we can skip doing it again here + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeReference assignTarget = firstInstruction.GetArg(0); + + input.RemoveRange(index, 2); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.Assign, [assignTarget])); + } +} + +// AppendNoPush [ref] +// PushReferenceValue [ref] +// -> Append [ref] +// These opcodes can be reduced to a single Append as long as the [ref]s are the same +internal sealed class AppendAndPushReferenceValue : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + + public ReadOnlySpan GetOpcodes() { + return [ + DreamProcOpcode.AppendNoPush, + DreamProcOpcode.PushReferenceValue + ]; + } + + /// + /// We can only apply this optimization if both opcodes refer to the same reference + /// + public bool CheckPreconditions(List input, int index) { + if (index + 1 >= input.Count) { + throw new ArgumentOutOfRangeException(nameof(index), "Index plus one is outside the bounds of the input list."); + } + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeInstruction secondInstruction = (AnnotatedBytecodeInstruction)(input[index + 1]); + + AnnotatedBytecodeReference appendTarget = firstInstruction.GetArg(0); + AnnotatedBytecodeReference pushTarget = secondInstruction.GetArg(0); + + return appendTarget.Equals(pushTarget); + } + + public void Apply(DMCompiler compiler, List input, int index) { + // We check the input bounds in CheckPreconditions, so we can skip doing it again here + + AnnotatedBytecodeInstruction firstInstruction = (AnnotatedBytecodeInstruction)(input[index]); + AnnotatedBytecodeReference appendTarget = firstInstruction.GetArg(0); + + input.RemoveRange(index, 2); + input.Insert(index, new AnnotatedBytecodeInstruction(DreamProcOpcode.Append, [appendTarget])); + } +} + // PushFloat [constant] // PushFloat [constant] // BitshiftLeft // -> PushFloat [result] -internal sealed class ConstFoldBitshiftLeft : IPeepholeOptimization { +internal sealed class ConstFoldBitshiftLeft : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -789,15 +663,15 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); - IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); // At runtime, given "A << B" we pop B then A // In the peephole optimizer, index is "A", index+1 is "B" var args = new List(1) {new AnnotatedBytecodeFloat(((int)pushVal1 << (int)pushVal2), firstInstruction.Location)}; - IPeepholeOptimization.ReplaceInstructions(input, index, 3, + IOptimization.ReplaceInstructions(input, index, 3, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } @@ -806,7 +680,9 @@ public void Apply(List input, int index) { // PushFloat [constant] // BitshiftRight // -> PushFloat [result] -internal sealed class ConstFoldBitshiftRight : IPeepholeOptimization { +internal sealed class ConstFoldBitshiftRight : IOptimization { + public OptPass OptimizationPass => OptPass.PeepholeOptimization; + public ReadOnlySpan GetOpcodes() { return [ DreamProcOpcode.PushFloat, @@ -815,15 +691,15 @@ public ReadOnlySpan GetOpcodes() { ]; } - public void Apply(List input, int index) { - var firstInstruction = IPeepholeOptimization.GetInstructionAndValue(input[index], out var pushVal1); - IPeepholeOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); + public void Apply(DMCompiler compiler, List input, int index) { + var firstInstruction = IOptimization.GetInstructionAndValue(input[index], out var pushVal1); + IOptimization.GetInstructionAndValue(input[index + 1], out var pushVal2); // At runtime, given "A >> B" we pop B then A // In the peephole optimizer, index is "A", index+1 is "B" var args = new List(1) {new AnnotatedBytecodeFloat(((int)pushVal1 >> (int)pushVal2), firstInstruction.Location)}; - IPeepholeOptimization.ReplaceInstructions(input, index, 3, + IOptimization.ReplaceInstructions(input, index, 3, new AnnotatedBytecodeInstruction(DreamProcOpcode.PushFloat, 1, args)); } } diff --git a/DMCompiler/Optimizer/PeepholeOptimizer.cs b/DMCompiler/Optimizer/PeepholeOptimizer.cs index a7f8588389..aad7cd7287 100644 --- a/DMCompiler/Optimizer/PeepholeOptimizer.cs +++ b/DMCompiler/Optimizer/PeepholeOptimizer.cs @@ -3,9 +3,13 @@ namespace DMCompiler.Optimizer; -internal interface IPeepholeOptimization { +/// +/// A single peephole optimization (e.g. const fold an operator) +/// +internal interface IOptimization { + public OptPass OptimizationPass { get; } public ReadOnlySpan GetOpcodes(); - public void Apply(List input, int index); + public void Apply(DMCompiler compiler, List input, int index); public bool CheckPreconditions(List input, int index) { return true; @@ -25,44 +29,66 @@ public static void ReplaceInstructions(List input, int index } } +/// +/// The list of peephole optimizer passes in the order that they should run +/// +internal enum OptPass : byte { + PeepholeOptimization = 0, // First-pass peephole optimizations (e.g. const folding) + BytecodeCompactor = 1, // Next-pass bytecode compacting (e.g. PushNFloats and other PushN opcodes) + ListCompactor = 2 // Final-pass list compacting (e.g. PushNFloats & CreateList -> CreateListNFloats) +} + +// ReSharper disable once ClassNeverInstantiated.Global internal sealed class PeepholeOptimizer { private class OptimizationTreeEntry { - public IPeepholeOptimization? Optimization; + public IOptimization? Optimization; public Dictionary? Children; } + /// + /// The optimization passes in the order that they run + /// + private static readonly OptPass[] Passes; + /// /// Trees matching chains of opcodes to peephole optimizations /// - private static readonly Dictionary OptimizationTrees = new(); + private static readonly Dictionary[] OptimizationTrees; - /// Setup static PeepholeOptimizer() { - var possibleTypes = typeof(PeepholeOptimizer).Assembly.GetTypes(); + Passes = (OptPass[])Enum.GetValues(typeof(OptPass)); + OptimizationTrees = new Dictionary[Passes.Length]; + for (int i = 0; i < OptimizationTrees.Length; i++) { + OptimizationTrees[i] = new Dictionary(); + } + } + + /// Setup for each + private static void GetOptimizations(DMCompiler compiler) { + var possibleTypes = typeof(IOptimization).Assembly.GetTypes(); var optimizationTypes = new List(possibleTypes.Length); + foreach (var type in possibleTypes) { - if (typeof(IPeepholeOptimization).IsAssignableFrom(type)) { + if (typeof(IOptimization).IsAssignableFrom(type) && type is { IsClass: true, IsAbstract: false }) { optimizationTypes.Add(type); } } foreach (var optType in optimizationTypes) { - if (optType.IsInterface || optType.IsAbstract) - continue; + var opt = (IOptimization)(Activator.CreateInstance(optType)!); - var opt = (IPeepholeOptimization)(Activator.CreateInstance(optType))!; var opcodes = opt.GetOpcodes(); if (opcodes.Length < 2) { - DMCompiler.ForcedError(Location.Internal, $"Peephole optimization {optType} must have at least 2 opcodes"); + compiler.ForcedError(Location.Internal, $"Peephole optimization {optType} must have at least 2 opcodes"); continue; } - if (!OptimizationTrees.TryGetValue(opcodes[0], out var treeEntry)) { + if (!OptimizationTrees[(byte)opt.OptimizationPass].TryGetValue(opcodes[0], out var treeEntry)) { treeEntry = new() { Children = new() }; - OptimizationTrees.Add(opcodes[0], treeEntry); + OptimizationTrees[(byte)opt.OptimizationPass].Add(opcodes[0], treeEntry); } for (int i = 1; i < opcodes.Length; i++) { @@ -81,7 +107,14 @@ static PeepholeOptimizer() { } } - public static void RunPeephole(List input) { + public static void RunPeephole(DMCompiler compiler, List input) { + GetOptimizations(compiler); + foreach (var optPass in Passes) { + RunPass(compiler, (byte)optPass, input); + } + } + + private static void RunPass(DMCompiler compiler, byte pass, List input) { OptimizationTreeEntry? currentOpt = null; int optSize = 0; @@ -92,7 +125,7 @@ int AttemptCurrentOpt(int i) { int offset; if (currentOpt.Optimization?.CheckPreconditions(input, i - optSize) is true) { - currentOpt.Optimization.Apply(input, i - optSize); + currentOpt.Optimization.Apply(compiler, input, i - optSize); offset = (optSize + 2); // Run over the new opcodes for potential further optimization } else { // This chain of opcodes did not lead to a valid optimization. @@ -116,7 +149,7 @@ int AttemptCurrentOpt(int i) { if (currentOpt == null) { optSize = 1; - OptimizationTrees.TryGetValue(opcode, out currentOpt); + OptimizationTrees[pass].TryGetValue(opcode, out currentOpt); continue; } diff --git a/DMCompiler/Program.cs b/DMCompiler/Program.cs index 0be1b68d1e..384f9441c1 100644 --- a/DMCompiler/Program.cs +++ b/DMCompiler/Program.cs @@ -6,18 +6,20 @@ namespace DMCompiler; internal struct Argument { /// The text we found that's in the '--whatever' format. May be null if no such text was present. public string? Name; + /// The value, either set in a '--whatever=whoever' format or just left by itself anonymously. May be null. public string? Value; } internal static class Program { private static void Main(string[] args) { - if (!TryParseArguments(args, out DMCompilerSettings settings)) { + DMCompiler compiler = new DMCompiler(); + if (!TryParseArguments(compiler, args, out DMCompilerSettings settings)) { Environment.Exit(1); return; } - if (!DMCompiler.Compile(settings)) { + if (!compiler.Compile(settings)) { //Compile errors, exit with an error code Environment.Exit(1); } @@ -34,6 +36,7 @@ private static IEnumerable StringArrayToArguments(string[] args) { retArgs.Add(new Argument { Value = firstString }); continue; } + firstString = firstString.TrimStart('-'); var split = firstString.Split('='); if(split.Length == 1) { // If it's a name-only argument @@ -43,9 +46,11 @@ private static IEnumerable StringArrayToArguments(string[] args) { retArgs.Add(new Argument {Name = firstString, Value = args[i] }); } } + retArgs.Add(new Argument { Name = firstString }); continue; } + retArgs.Add(new Argument { Name = split[0], Value = split[1] }); } @@ -74,7 +79,7 @@ private static void PrintHelp() { Console.WriteLine("--pragma-config [file].dm : Configure the error/warning/notice/ignore level of compiler messages"); } - private static bool TryParseArguments(string[] args, out DMCompilerSettings settings) { + private static bool TryParseArguments(DMCompiler compiler, string[] args, out DMCompilerSettings settings) { settings = new DMCompilerSettings { Files = new List() }; @@ -91,6 +96,7 @@ private static bool TryParseArguments(string[] args, out DMCompilerSettings sett case "dump-preprocessor": settings.DumpPreprocessor = true; break; case "no-standard": settings.NoStandard = true; break; case "verbose": settings.Verbose = true; break; + case "print-code-tree": settings.PrintCodeTree = true; break; case "skip-bad-args": break; case "define": var parts = arg.Value?.Split('=', 2); // Only split on the first = in case of stuff like "--define AAA=0==1" @@ -98,6 +104,7 @@ private static bool TryParseArguments(string[] args, out DMCompilerSettings sett Console.WriteLine("Compiler arg 'define' requires macro identifier for definition directive"); return false; } + settings.MacroDefines ??= new Dictionary(); settings.MacroDefines[parts[0]] = parts.Length > 1 ? parts[1] : ""; break; @@ -108,21 +115,24 @@ private static bool TryParseArguments(string[] args, out DMCompilerSettings sett case "pragma-config": { if(arg.Value is null || !HasValidDMExtension(arg.Value)) { if(skipBad) { - DMCompiler.ForcedWarning($"Compiler arg 'pragma-config' requires filename of valid DM file, skipping"); + compiler.ForcedWarning($"Compiler arg 'pragma-config' requires filename of valid DM file, skipping"); continue; } + Console.WriteLine("Compiler arg 'pragma-config' requires filename of valid DM file"); return false; } + settings.PragmaFileOverride = arg.Value; break; } case "version": { if(arg.Value is null) { if(skipBad) { - DMCompiler.ForcedWarning("Compiler arg 'version' requires a full BYOND build (e.g. --version=514.1584), skipping"); + compiler.ForcedWarning("Compiler arg 'version' requires a full BYOND build (e.g. --version=514.1584), skipping"); continue; } + Console.WriteLine("Compiler arg 'version' requires a full BYOND build (e.g. --version=514.1584)"); return false; } @@ -130,9 +140,10 @@ private static bool TryParseArguments(string[] args, out DMCompilerSettings sett var split = arg.Value.Split('.', StringSplitOptions.RemoveEmptyEntries); if (split.Length != 2 || !int.TryParse(split[0], out _) || !int.TryParse(split[1], out _)) { // We want to make sure that they *are* ints but the preprocessor takes strings if(skipBad) { - DMCompiler.ForcedWarning("Compiler arg 'version' requires a full BYOND build (e.g. --version=514.1584), skipping"); + compiler.ForcedWarning("Compiler arg 'version' requires a full BYOND build (e.g. --version=514.1584), skipping"); continue; } + Console.WriteLine("Compiler arg 'version' requires a full BYOND build (e.g. --version=514.1584)"); return false; } @@ -148,8 +159,9 @@ private static bool TryParseArguments(string[] args, out DMCompilerSettings sett settings.Files.Add(arg.Value); break; } + if (skipBad) { - DMCompiler.ForcedWarning($"Invalid compiler arg '{arg.Value}', skipping"); + compiler.ForcedWarning($"Invalid compiler arg '{arg.Value}', skipping"); } else { Console.WriteLine($"Invalid arg '{arg}'"); return false; @@ -159,11 +171,11 @@ private static bool TryParseArguments(string[] args, out DMCompilerSettings sett } default: { if (skipBad) { - DMCompiler.ForcedWarning($"Unknown compiler arg '{arg.Name}', skipping"); + compiler.ForcedWarning($"Unknown compiler arg '{arg.Name}', skipping"); break; } - Console.WriteLine($"Unknown arg '{arg}'"); + Console.WriteLine($"Unknown arg '{arg.Name}'"); return false; } } diff --git a/DMDisassembler/Program.cs b/DMDisassembler/Program.cs index 36fdff2cf2..638358c600 100644 --- a/DMDisassembler/Program.cs +++ b/DMDisassembler/Program.cs @@ -230,11 +230,13 @@ private static void LoadAllTypes() { } //Add global procs to the root type - DMType globalType = AllTypes["/"]; - foreach (int procId in CompiledJson.GlobalProcs) { - var proc = Procs[procId]; + if (CompiledJson.GlobalProcs != null) { + DMType globalType = AllTypes["/"]; + foreach (int procId in CompiledJson.GlobalProcs) { + var proc = Procs[procId]; - globalType.Procs.Add(proc.Name, proc); + globalType.Procs.Add(proc.Name, proc); + } } } diff --git a/OpenDreamRuntime/AtomManager.cs b/OpenDreamRuntime/AtomManager.cs index e942eec318..0a0a7c2e24 100644 --- a/OpenDreamRuntime/AtomManager.cs +++ b/OpenDreamRuntime/AtomManager.cs @@ -239,11 +239,11 @@ public bool IsValidAppearanceVar(string name) { case "transform": case "appearance": case "verbs": - return true; - - // Get/SetAppearanceVar doesn't handle these case "overlays": case "underlays": + return true; + + // Get/SetAppearanceVar doesn't handle filters right now case "filters": default: return false; @@ -378,8 +378,14 @@ public void SetAppearanceVar(MutableIconAppearance appearance, string varName, D break; case "appearance": throw new Exception("Cannot assign the appearance var on an appearance"); - // TODO: overlays, underlays, filters - // Those are handled separately by whatever is calling SetAppearanceVar currently + + // These should be handled by the DreamObject if being accessed through that + case "overlays": + case "underlays": + throw new Exception($"Cannot assign the {varName} var on an appearance"); + + // TODO: filters + // It's handled separately by whatever is calling SetAppearanceVar currently default: throw new ArgumentException($"Invalid appearance var {varName}"); } @@ -465,8 +471,26 @@ public DreamValue GetAppearanceVar(ImmutableIconAppearance appearance, string va case "appearance": MutableIconAppearance appearanceCopy = appearance.ToMutable(); // Return a copy return new(appearanceCopy); - // TODO: overlays, underlays, filters - // Those are handled separately by whatever is calling GetAppearanceVar currently + + // These should be handled by an atom if referenced through one + case "overlays": + case "underlays": + // In BYOND this just creates a new normal list + var lays = varName == "overlays" ? appearance.Overlays : appearance.Underlays; + var list = _objectTree.CreateList(lays.Count); + + if (_appearanceSystem != null) { + foreach (var layId in lays) { + var lay = _appearanceSystem.MustGetAppearance(layId); + + list.AddValue(new(lay)); + } + } + + return new(list); + + // TODO: filters + // It's handled separately by whatever is calling GetAppearanceVar currently default: throw new ArgumentException($"Invalid appearance var {varName}"); } diff --git a/OpenDreamRuntime/DreamConnection.cs b/OpenDreamRuntime/DreamConnection.cs index 760def1828..0b1a1e5668 100644 --- a/OpenDreamRuntime/DreamConnection.cs +++ b/OpenDreamRuntime/DreamConnection.cs @@ -29,8 +29,9 @@ public sealed class DreamConnection { [ViewVariables] public ICommonSession? Session { get; private set; } [ViewVariables] public DreamObjectClient? Client { get; private set; } - [ViewVariables] - public DreamObjectMob? Mob { + [ViewVariables] public string Key { get; private set; } + + [ViewVariables] public DreamObjectMob? Mob { get => _mob; set { if (_mob != value) { @@ -54,15 +55,14 @@ public DreamObjectMob? Mob { _mob.Connection.Mob = null; _mob.Connection = this; - _mob.Key = Session!.Name; + _mob.Key = Key; _mob.SpawnProc("Login", usr: _mob); } } } } - [ViewVariables] - public DreamObjectMovable? Eye { + [ViewVariables] public DreamObjectMovable? Eye { get => _eye; set { _eye = value; @@ -93,8 +93,9 @@ public string? SelectedStatPanel { } } - public DreamConnection() { + public DreamConnection(string key) { IoCManager.InjectDependencies(this); + Key = key; _entitySystemManager.TryGetEntitySystem(out _screenOverlaySystem); _entitySystemManager.TryGetEntitySystem(out _clientImagesSystem); diff --git a/OpenDreamRuntime/DreamManager.Connections.cs b/OpenDreamRuntime/DreamManager.Connections.cs index b8bc9cee8d..935518968d 100644 --- a/OpenDreamRuntime/DreamManager.Connections.cs +++ b/OpenDreamRuntime/DreamManager.Connections.cs @@ -257,7 +257,7 @@ private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e) { case SessionStatus.InGame: { if (!_connections.TryGetValue(e.Session.UserId, out var connection)) { - connection = new DreamConnection(); + connection = new DreamConnection(e.Session.Name); _connections.Add(e.Session.UserId, connection); } diff --git a/OpenDreamRuntime/DreamManager.cs b/OpenDreamRuntime/DreamManager.cs index a9b14b663b..deb4aef333 100644 --- a/OpenDreamRuntime/DreamManager.cs +++ b/OpenDreamRuntime/DreamManager.cs @@ -39,7 +39,7 @@ public sealed partial class DreamManager { private ServerAppearanceSystem? _appearanceSystem; - public DreamObjectWorld WorldInstance { get; private set; } + public DreamObjectWorld WorldInstance { get; set; } public Exception? LastDMException { get; set; } public event EventHandler? OnException; @@ -158,8 +158,6 @@ public bool LoadJson(string? jsonPath) { } } - Globals[GlobalNames.IndexOf("world")] = new DreamValue(WorldInstance); - _dreamMapManager.LoadMaps(_compiledJson.Maps); var aczProvider = new DreamAczProvider(_dependencyCollection, rootPath, resources); diff --git a/OpenDreamRuntime/DreamMapManager.cs b/OpenDreamRuntime/DreamMapManager.cs index 0f765e2091..b8588c36d7 100644 --- a/OpenDreamRuntime/DreamMapManager.cs +++ b/OpenDreamRuntime/DreamMapManager.cs @@ -139,7 +139,7 @@ public void InitializeAtoms(List? maps) { for (var z = 1; z <= Levels; ++z) { for (var y = Size.Y; y >= 1; --y) { for (var x = Size.X; x >= 1; --x) { - _levels[z - 1].Cells[x - 1, y - 1].Turf?.SpawnProc("New"); + _levels[z - 1].Cells[x - 1, y - 1].Turf.SpawnProc("New"); } } } @@ -154,30 +154,18 @@ public void InitializeAtoms(List? maps) { } } - private DreamObject SetTurf(Vector2i pos, int z, DreamObjectDefinition type, DreamProcArguments creationArguments, DreamObjectArea? area = null) { + private void SetTurf(Vector2i pos, int z, DreamObjectDefinition type, DreamProcArguments creationArguments) { if (IsInvalidCoordinate(pos, z)) throw new ArgumentException("Invalid coordinates"); - Cell cell = _levels[z - 1].Cells[pos.X - 1, pos.Y - 1]; + var cell = _levels[z - 1].Cells[pos.X - 1, pos.Y - 1]; - if(area is not null) { - cell.Area.Contents.RemoveValue(new(cell.Turf)); - area.Contents.AddValue(new(cell.Turf)); - } - - if (cell.Turf != null) { - cell.Turf.SetTurfType(type); - } else { - cell.Turf = new DreamObjectTurf(type, pos.X, pos.Y, z, cell); - // Only add the /turf to .contents when it's created. - cell.Area.Contents.AddValue(new(cell.Turf)); - } + cell.Turf.SetTurfType(type); MutableIconAppearance turfAppearance = _atomManager.GetAppearanceFromDefinition(cell.Turf.ObjectDefinition); SetTurfAppearance(cell.Turf, turfAppearance); cell.Turf.InitSpawn(creationArguments); - return cell.Turf; } public void SetTurf(DreamObjectTurf turf, DreamObjectDefinition type, DreamProcArguments creationArguments) { @@ -221,7 +209,7 @@ public void SetAreaAppearance(DreamObjectArea area, MutableIconAppearance appear //for each turf, update the appropriate ID Dictionary oldToNewAppearance = new(); - foreach (var turf in area.Contents.GetTurfs()) { + foreach (var turf in area.Turfs) { if(oldToNewAppearance.TryGetValue(turf.Appearance, out var newAppearance)) turf.Appearance = newAppearance; else { @@ -253,7 +241,7 @@ public bool TryGetCellAt(Vector2i pos, int z, [NotNullWhen(true)] out Cell? cell public bool TryGetTurfAt(Vector2i pos, int z, [NotNullWhen(true)] out DreamObjectTurf? turf) { if (TryGetCellAt(pos, z, out var cell)) { turf = cell.Turf; - return (turf != null); + return true; } turf = null; @@ -279,8 +267,6 @@ public void SetWorldSize(Vector2i size) { var newX = Math.Max(oldSize.X, size.X); var newY = Math.Max(oldSize.Y, size.Y); - DreamObjectArea defaultArea = GetOrCreateArea(_defaultArea); - Size = (newX, newY); if(Size.X > oldSize.X || Size.Y > oldSize.Y) { @@ -295,7 +281,10 @@ public void SetWorldSize(Vector2i size) { continue; } - existingLevel.Cells[x - 1, y - 1] = new Cell(defaultArea); + var defaultTurf = new DreamObjectTurf(_defaultTurf.ObjectDefinition, x, y, existingLevel.Z); + var cell = new Cell(DefaultArea, defaultTurf); + defaultTurf.Cell = cell; + existingLevel.Cells[x - 1, y - 1] = cell; SetTurf(new Vector2i(x, y), existingLevel.Z, _defaultTurf.ObjectDefinition, new()); } } @@ -313,7 +302,7 @@ public void SetWorldSize(Vector2i size) { for (var y = 1; y <= oldSize.Y; y++) { if (x > size.X || y > size.Y) { var deleteCell = oldCells[x - 1, y - 1]; - deleteCell.Turf?.Delete(); + deleteCell.Turf.Delete(); _mapSystem.SetTile(existingLevel.Grid, new Vector2i(x, y), Tile.Empty); foreach (var movableToDelete in deleteCell.Movables) { movableToDelete.Delete(); @@ -329,14 +318,12 @@ public void SetWorldSize(Vector2i size) { public void SetZLevels(int levels) { if (levels > Levels) { - DreamObjectArea defaultArea = GetOrCreateArea(_defaultArea); - for (int z = Levels + 1; z <= levels; z++) { MapId mapId = new(z); _mapSystem.CreateMap(mapId); var grid = _mapManager.CreateGridEntity(mapId); - Level level = new Level(z, grid, defaultArea, Size); + Level level = new Level(z, grid, _defaultTurf.ObjectDefinition, DefaultArea, Size); _levels.Add(level); for (int x = 1; x <= Size.X; x++) { @@ -373,13 +360,9 @@ private void LoadMapAreasAndTurfs(MapBlockJson block, Dictionary block.Width) { @@ -451,14 +434,18 @@ public sealed class Level { public Cell[,] Cells; public readonly Dictionary QueuedTileUpdates = new(); - public Level(int z, Entity grid, DreamObjectArea area, Vector2i size) { + public Level(int z, Entity grid, DreamObjectDefinition turfType, DreamObjectArea area, Vector2i size) { Z = z; Grid = grid; Cells = new Cell[size.X, size.Y]; for (int x = 0; x < size.X; x++) { for (int y = 0; y < size.Y; y++) { - Cells[x, y] = new Cell(area); + var turf = new DreamObjectTurf(turfType, x + 1, y + 1, z); + var cell = new Cell(area, turf); + + turf.Cell = cell; + Cells[x, y] = cell; } } } @@ -468,20 +455,24 @@ public sealed class Cell { public DreamObjectArea Area { get => _area; set { + _area.Turfs.Remove(Turf); _area.ResetCoordinateCache(); + _area = value; + _area.Turfs.Add(Turf); _area.ResetCoordinateCache(); } } - public DreamObjectTurf? Turf; + public readonly DreamObjectTurf Turf; public readonly List Movables = new(); private DreamObjectArea _area; - public Cell(DreamObjectArea area) { + public Cell(DreamObjectArea area, DreamObjectTurf turf) { + Turf = turf; _area = area; - _area.ResetCoordinateCache(); + Area = area; } } diff --git a/OpenDreamRuntime/DreamThread.cs b/OpenDreamRuntime/DreamThread.cs index 259cc28db3..c9c9212084 100644 --- a/OpenDreamRuntime/DreamThread.cs +++ b/OpenDreamRuntime/DreamThread.cs @@ -261,6 +261,7 @@ public DreamValue ReentrantResume(ProcState? untilState, out ProcStatus resultSt resultStatus = ProcStatus.Cancelled; return default; } + CancelAll(); HandleException(dmError); status = ProcStatus.Cancelled; diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs index 14d030aa3b..cfa73be66b 100644 --- a/OpenDreamRuntime/Objects/DreamObject.cs +++ b/OpenDreamRuntime/Objects/DreamObject.cs @@ -344,7 +344,7 @@ public string GetDisplayName(StringFormatEncoder.FormatSuffix? suffix = null) { // /client is a little special and will return its key var // TODO: Maybe this should be an override to GetDisplayName()? if (this is DreamObjectClient client) - return client.Connection.Session!.Name; + return client.Connection.Key; var name = GetRawName(); bool isProper = StringIsProper(name); diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index 2c4789822d..e17b2340e8 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -174,7 +174,8 @@ public virtual void Cut(int start = 1, int end = 0) { _associativeValues.Remove(_values[i - 1]); } - _values.RemoveRange(start - 1, end - start); + if (end > start) + _values.RemoveRange(start - 1, end - start); } public void Insert(int index, DreamValue value) { @@ -409,16 +410,17 @@ public override bool ContainsValue(DreamValue value) { } public override DreamValue GetValue(DreamValue key) { - if (!key.TryGetValueAsString(out var varName)) { - throw new Exception($"Invalid var index {key}"); - } + if (key.TryGetValueAsInteger(out int keyInteger)) { + return new DreamValue(DreamObject.GetVariableNames().ElementAt(keyInteger - 1)); //1-indexed + } else if (key.TryGetValueAsString(out var varName)) { + if (DreamObject.TryGetVariable(varName, out var objectVar)) { + return objectVar; + } - if (!DreamObject.TryGetVariable(varName, out var objectVar)) { - throw new Exception( - $"Cannot get value of undefined var \"{key}\" on type {DreamObject.ObjectDefinition.Type}"); + throw new Exception($"Cannot get value of undefined var \"{key}\" on type {DreamObject.ObjectDefinition.Type}"); + } else { + throw new Exception($"Invalid var index {key}"); } - - return objectVar; } public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { @@ -1134,27 +1136,23 @@ public override int GetLength() { } // turf.contents list -public sealed class TurfContentsList : DreamList { - private readonly IDreamMapManager.Cell _cell; - - public TurfContentsList(DreamObjectDefinition listDef, IDreamMapManager.Cell cell) : base(listDef, 0) { - _cell = cell; - } +public sealed class TurfContentsList(DreamObjectDefinition listDef, DreamObjectTurf turf) : DreamList(listDef, 0) { + private IDreamMapManager.Cell Cell => turf.Cell; public override DreamValue GetValue(DreamValue key) { if (!key.TryGetValueAsInteger(out var index)) throw new Exception($"Invalid index into turf contents list: {key}"); - if (index < 1 || index > _cell.Movables.Count) + if (index < 1 || index > Cell.Movables.Count) throw new Exception($"Out of bounds index on turf contents list: {index}"); - return new DreamValue(_cell.Movables[index - 1]); + return new DreamValue(Cell.Movables[index - 1]); } // TODO: This would preferably be an IEnumerable<> method. Probably as part of #985. public override List GetValues() { - List values = new(_cell.Movables.Count); + List values = new(Cell.Movables.Count); - foreach (var movable in _cell.Movables) { + foreach (var movable in Cell.Movables) { values.Add(new(movable)); } @@ -1169,32 +1167,30 @@ public override void AddValue(DreamValue value) { if (!value.TryGetValueAsDreamObject(out var movable)) throw new Exception($"Cannot add {value} to turf contents"); - movable.SetVariable("loc", new(_cell.Turf)); + movable.SetVariable("loc", new(Cell.Turf)); } public override void Cut(int start = 1, int end = 0) { - int movableCount = _cell.Movables.Count + 1; + int movableCount = Cell.Movables.Count + 1; if (end == 0 || end > movableCount) end = movableCount; for (int i = start; i < end; i++) { - _cell.Movables[i - 1].SetVariable("loc", DreamValue.Null); + Cell.Movables[i - 1].SetVariable("loc", DreamValue.Null); } } public override int GetLength() { - return _cell.Movables.Count; + return Cell.Movables.Count; } } // area.contents list public sealed class AreaContentsList(DreamObjectDefinition listDef, DreamObjectArea area) : DreamList(listDef, 0) { - private readonly List _turfs = new(); - public override DreamValue GetValue(DreamValue key) { if (!key.TryGetValueAsInteger(out var index)) throw new Exception($"Invalid index into area contents list: {key}"); - foreach (var turf in _turfs) { + foreach (var turf in area.Turfs) { if (index < 1) break; @@ -1215,9 +1211,9 @@ public override DreamValue GetValue(DreamValue key) { } public override List GetValues() { - List values = new(_turfs.Count); + List values = new(area.Turfs.Count); - foreach (var turf in _turfs) { + foreach (var turf in area.Turfs) { values.Add(new(turf)); values.AddRange(turf.Contents.GetValues()); } @@ -1234,14 +1230,12 @@ public override void AddValue(DreamValue value) { throw new Exception($"Cannot add {value} to area contents"); turf.Cell.Area = area; - _turfs.Add(turf); } public override void RemoveValue(DreamValue value) { if (!value.TryGetValueAsDreamObject(out var turf)) throw new Exception($"Cannot remove {value} from area contents"); - _turfs.Remove(turf); // Remove first, in case the new area (default) is still this area turf.Cell.Area = DreamMapManager.DefaultArea; } @@ -1250,41 +1244,31 @@ public override void Cut(int start = 1, int end = 0) { } public override int GetLength() { - int length = _turfs.Count; + int length = area.Turfs.Count; - foreach (var turf in _turfs) + foreach (var turf in area.Turfs) length += turf.Contents.GetLength(); return length; } - - public IEnumerable GetTurfs() { - return _turfs; - } } // proc args list -sealed class ProcArgsList : DreamList { - private readonly DMProcState _state; - - public ProcArgsList(DreamObjectDefinition listDef, DMProcState state) : base(listDef, 0) { - _state = state; - } - +internal sealed class ProcArgsList(DreamObjectDefinition listDef, DMProcState state) : DreamList(listDef, 0) { public override DreamValue GetValue(DreamValue key) { if (!key.TryGetValueAsInteger(out var index)) throw new Exception($"Invalid index into args list: {key}"); - if (index < 1 || index > _state.ArgumentCount) + if (index < 1 || index > state.ArgumentCount) throw new Exception($"Out of bounds index on args list: {index}"); - return _state.GetArguments()[index - 1]; + return state.GetArguments()[index - 1]; } // TODO: This would preferably be an IEnumerable<> method. Probably as part of #985. public override List GetValues() { - List values = new(_state.ArgumentCount); + List values = new(state.ArgumentCount); - foreach (DreamValue value in _state.GetArguments()) { + foreach (DreamValue value in state.GetArguments()) { values.Add(value); } @@ -1294,10 +1278,10 @@ public override List GetValues() { public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { if (!key.TryGetValueAsInteger(out var index)) throw new Exception($"Invalid index into args list: {key}"); - if (index < 1 || index > _state.ArgumentCount) + if (index < 1 || index > state.ArgumentCount) throw new Exception($"Out of bounds index on args list: {index}"); - _state.SetArgument(index - 1, value); + state.SetArgument(index - 1, value); } public override void AddValue(DreamValue value) { @@ -1313,7 +1297,7 @@ public override void Cut(int start = 1, int end = 0) { } public override int GetLength() { - return _state.ArgumentCount; + return state.ArgumentCount; } } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectArea.cs b/OpenDreamRuntime/Objects/Types/DreamObjectArea.cs index bdaa3dfc39..3c1e2bafa0 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectArea.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectArea.cs @@ -24,16 +24,19 @@ public int Z { } } - public readonly AreaContentsList Contents; public ImmutableIconAppearance Appearance; + public readonly HashSet Turfs; + + private readonly AreaContentsList _contents; // Iterating all our turfs to find the one with the lowest coordinates is slow business private int? _cachedX, _cachedY, _cachedZ; public DreamObjectArea(DreamObjectDefinition objectDefinition) : base(objectDefinition) { - Contents = new(ObjectTree.List.ObjectDefinition, this); Appearance = AppearanceSystem!.DefaultAppearance; + Turfs = new(); AtomManager.SetAtomAppearance(this, AtomManager.GetAppearanceFromDefinition(ObjectDefinition)); + _contents = new(ObjectTree.List.ObjectDefinition, this); } /// @@ -55,7 +58,7 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new(Z); return true; case "contents": - value = new(Contents); + value = new(_contents); return true; default: return base.TryGetVar(varName, out value); @@ -109,7 +112,7 @@ private void UpdateCoordinateCache() { if (_cachedX != null) return; - foreach (var turf in Contents.GetTurfs()) { + foreach (var turf in Turfs) { if (_cachedX != null) { if (turf.Z > _cachedZ) continue; diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs index 55b944f0fc..320c5d3b4a 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs @@ -41,10 +41,10 @@ protected override void HandleDeletion(bool possiblyThreaded) { protected override bool TryGetVar(string varName, out DreamValue value) { switch (varName) { case "ckey": - value = new(DreamProcNativeHelpers.Ckey(Connection.Session!.Name)); + value = new(DreamProcNativeHelpers.Ckey(Connection.Key)); return true; case "key": - value = new(Connection.Session!.Name); + value = new(Connection.Key); return true; case "mob": value = new(Connection.Mob); @@ -68,7 +68,7 @@ protected override bool TryGetVar(string varName, out DreamValue value) { MD5 md5 = MD5.Create(); // Check on Robust.Shared.Network.NetUserData.HWId" if you want to seed from how RT does user identification. // We don't use it here because it is probably not enough to ensure security, and (as of time of writing) only works on Windows machines. - byte[] brown = Encoding.UTF8.GetBytes(Connection.Session!.Name); + byte[] brown = Encoding.UTF8.GetBytes(Connection.Key); byte[] hash = md5.ComputeHash(brown); string hashStr = BitConverter.ToString(hash).Replace("-", "").ToLower().Substring(0,15); // Extracting the first 15 digits to ensure it'll fit in a 64-bit number diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectMob.cs b/OpenDreamRuntime/Objects/Types/DreamObjectMob.cs index 30fc7e79bd..ae5d9876d3 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectMob.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectMob.cs @@ -82,13 +82,12 @@ protected override void SetVar(string varName, DreamValue value) { Key = DreamProcNativeHelpers.Ckey(Key); foreach (var connection in DreamManager.Connections) { - if (DreamProcNativeHelpers.Ckey(connection.Session!.Name) == Key) { + if (DreamProcNativeHelpers.Ckey(connection.Key) == Key) { connection.Mob = this; break; } } - break; case "see_invisible": value.TryGetValueAsInteger(out int seeVis); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectTurf.cs b/OpenDreamRuntime/Objects/Types/DreamObjectTurf.cs index 729d3fa884..f0ae5308ad 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectTurf.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectTurf.cs @@ -4,17 +4,17 @@ namespace OpenDreamRuntime.Objects.Types; public sealed class DreamObjectTurf : DreamObjectAtom { public readonly int X, Y, Z; - public readonly IDreamMapManager.Cell Cell; public readonly TurfContentsList Contents; public ImmutableIconAppearance Appearance; + public IDreamMapManager.Cell Cell; - public DreamObjectTurf(DreamObjectDefinition objectDefinition, int x, int y, int z, IDreamMapManager.Cell cell) : base(objectDefinition) { + public DreamObjectTurf(DreamObjectDefinition objectDefinition, int x, int y, int z) : base(objectDefinition) { X = x; Y = y; Z = z; - Cell = cell; - Contents = new TurfContentsList(ObjectTree.List.ObjectDefinition, Cell); AtomManager.SetAtomAppearance(this, AtomManager.GetAppearanceFromDefinition(ObjectDefinition)); + Cell = default!; // NEEDS to be set by DreamMapManager after creation + Contents = new TurfContentsList(ObjectTree.List.ObjectDefinition, this); } public void SetTurfType(DreamObjectDefinition objectDefinition) { diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs index 662241b9d7..0dd50ac411 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs @@ -100,6 +100,15 @@ protected override void HandleDeletion(bool possiblyThreaded) { _server.Shutdown("world was deleted"); } + ~DreamObjectWorld() { + if (this != DreamManager.WorldInstance) { + Deleted = true; + return; + } + + Delete(true); + } + protected override bool TryGetVar(string varName, out DreamValue value) { switch (varName) { case "log": @@ -205,9 +214,9 @@ protected override bool TryGetVar(string varName, out DreamValue value) { case "system_type": //system_type value should match the defines in Defines.dm if (Environment.OSVersion.Platform is PlatformID.Unix or PlatformID.MacOSX or PlatformID.Other) - value = new DreamValue(0); + value = new DreamValue("UNIX"); else - value = new DreamValue(1); //Windows + value = new DreamValue("MS_WINDOWS"); //Windows return true; diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 36bcf69136..483b4aa5e4 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -18,6 +18,7 @@ namespace OpenDreamRuntime.Procs { internal static class DMOpcodeHandlers { #region Values + public static ProcStatus PushReferenceValue(DMProcState state) { DreamReference reference = state.ReadReference(); @@ -46,7 +47,6 @@ public static ProcStatus AssignInto(DMProcState state) { return ProcStatus.Continue; } - public static ProcStatus CreateList(DMProcState state) { int size = state.ReadInt(); var list = state.Proc.ObjectTree.CreateList(size); @@ -59,6 +59,17 @@ public static ProcStatus CreateList(DMProcState state) { return ProcStatus.Continue; } + public static ProcStatus CreateMultidimensionalList(DMProcState state) { + var dimensionCount = state.ReadInt(); + var list = state.Proc.ObjectTree.CreateList(); + var dimensionSizes = state.PopCount(dimensionCount); + + // Same as new /list(1, 2, 3) + list.Initialize(new(dimensionSizes)); + state.Push(new DreamValue(list)); + return ProcStatus.Continue; + } + public static ProcStatus CreateAssociativeList(DMProcState state) { int size = state.ReadInt(); var list = state.Proc.ObjectTree.CreateList(size); @@ -2409,7 +2420,19 @@ public static ProcStatus OutputControl(DMProcState state) { foreach (var connection in state.DreamManager.Connections) { connection.OutputControl(message, control); } + } else if (receiver is DreamList list) { + // Output to every mob in the left-hand list. + foreach (var entry in list.GetValues()) { + if (entry.TryGetValueAsDreamObject(out var entryObj)) { + if (entryObj is DreamObjectMob entryMob) { + entryMob.Connection?.OutputControl(message, control); + } else if (entryObj is DreamObjectClient entryClient) { + entryClient.Connection.OutputControl(message, control); + } + } + } } else { + // TODO: BYOND's behavior is to ignore rather than throw here throw new Exception($"Invalid output() recipient: {receiver}"); } diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index 40e15fa885..1ab4d5c0dd 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -229,6 +229,7 @@ public sealed class DMProcState : ProcState { {DreamProcOpcode.Combine, DMOpcodeHandlers.Combine}, {DreamProcOpcode.CreateObject, DMOpcodeHandlers.CreateObject}, {DreamProcOpcode.BooleanOr, DMOpcodeHandlers.BooleanOr}, + {DreamProcOpcode.CreateMultidimensionalList, DMOpcodeHandlers.CreateMultidimensionalList}, {DreamProcOpcode.CompareGreaterThanOrEqual, DMOpcodeHandlers.CompareGreaterThanOrEqual}, {DreamProcOpcode.SwitchCase, DMOpcodeHandlers.SwitchCase}, {DreamProcOpcode.Mask, DMOpcodeHandlers.Mask}, @@ -486,6 +487,8 @@ public ProcStatus Call(DreamProc proc, DreamObject? src, DreamProcArguments argu var state = proc.CreateState(Thread, src, Usr, arguments); Thread.PushProcState(state); + if (proc is AsyncNativeProc) // Hack to ensure sleeping native procs will return our value in a no-waitfor context + state.Result = Result; return ProcStatus.Called; } @@ -660,6 +663,7 @@ public DreamReference ReadReference() { case DMReference.Type.Self: case DMReference.Type.Usr: case DMReference.Type.Args: + case DMReference.Type.World: case DMReference.Type.SuperProc: case DMReference.Type.ListIndex: return new DreamReference(refType, 0); @@ -687,32 +691,10 @@ private static void ThrowInvalidReferenceType(DMReference.Type type) { public (DMCallArgumentsType Type, int StackSize) ReadProcArguments() { return ((DMCallArgumentsType) ReadByte(), ReadInt()); } + #endregion #region References - public bool IsNullDereference(DreamReference reference) { - switch (reference.Type) { - case DMReference.Type.Field: { - if (Peek().IsNull) { - PopDrop(); - return true; - } - - return false; - } - case DMReference.Type.ListIndex: { - DreamValue list = _stack[_stackIndex - 2]; - if (list.IsNull) { - PopDrop(); - PopDrop(); - return true; - } - - return false; - } - default: throw new Exception($"Invalid dereference type {reference.Type}"); - } - } /// /// Takes a DMReference with a type and returns the value being indexed @@ -753,6 +735,7 @@ public void AssignReference(DreamReference reference, DreamValue value) { if (!value.TryGetValueAsDreamObject(out Usr)) { ThrowCannotAssignUsrTo(value); } + break; case DMReference.Type.Field: { DreamValue owner = Pop(); @@ -813,6 +796,7 @@ public DreamValue GetReferenceValue(DreamReference reference, bool peek = false) case DMReference.Type.Argument: return _localVariables[reference.Value]; case DMReference.Type.Local: return _localVariables[ArgumentCount + reference.Value]; case DMReference.Type.Args: return new(new ProcArgsList(Proc.ObjectTree.List.ObjectDefinition, this)); + case DMReference.Type.World: return new(DreamManager.WorldInstance); case DMReference.Type.Field: { DreamValue owner = peek ? Peek() : Pop(); @@ -850,7 +834,7 @@ private static void ThrowCannotGetFieldSrcGlobalProc(string fieldName) { [MethodImpl(MethodImplOptions.NoInlining)] private void ThrowTypeHasNoField(string fieldName) { - throw new Exception($"Type {Instance!.ObjectDefinition!.Type} has no field called \"{fieldName}\""); + throw new Exception($"Type {Instance!.ObjectDefinition.Type} has no field called \"{fieldName}\""); } public void PopReference(DreamReference reference) { diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs index 9e3b56062d..a06a823d57 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRegex.cs @@ -53,11 +53,12 @@ public static DreamValue NativeProc_Find(NativeProc.Bundle bundle, DreamObject? if (dreamRegex.IsGlobal) { dreamRegex.SetVariable("next", DreamValue.Null); } + return new DreamValue(0); } } - public static async Task RegexReplace(AsyncNativeProc.State state, DreamObject regexInstance, DreamValue haystack, DreamValue replace, int start, int end) { + public static DreamValue RegexReplace(DreamObject regexInstance, DreamValue haystack, DreamValue replace, int start, int end) { DreamObjectRegex regex = (DreamObjectRegex)regexInstance; if (!haystack.TryGetValueAsString(out var haystackString)) { @@ -68,7 +69,7 @@ public static async Task RegexReplace(AsyncNativeProc.State state, D if (end != 0) haystackSubstring = haystackString.Substring(0, end - start); if (replace.TryGetValueAsProc(out DreamProc? replaceProc)) { - return await DoProcReplace(state, replaceProc); + return DoProcReplace(replaceProc); } if (replace.TryGetValueAsString(out var replaceString)) { @@ -77,7 +78,7 @@ public static async Task RegexReplace(AsyncNativeProc.State state, D throw new ArgumentException("Replacement argument must be a string or a proc"); - async Task DoProcReplace(AsyncNativeProc.State state, DreamProc proc) { + DreamValue DoProcReplace(DreamProc proc) { var currentStart = Math.Max(start - 1, 0); var currentHaystack = haystackSubstring; while (currentStart < currentHaystack.Length) { @@ -90,7 +91,10 @@ async Task DoProcReplace(AsyncNativeProc.State state, DreamProc proc args[i] = new DreamValue(groups[i].Value); } - var result = await state.CallNoWait(proc, null, null, args); + // TODO: src is the regex string + // TODO: We need to add this to our current thread instead of spawning a new one + // TODO: This call needs to immediately die upon sleeping + var result = proc.Spawn(null, new(args)); var replacement = result.Stringify(); currentHaystack = regex.Regex.Replace(currentHaystack, replacement, 1, currentStart); @@ -131,13 +135,13 @@ DreamValue DoTextReplace(string replacement) { [DreamProcParameter("replacement", Type = DreamValue.DreamValueTypeFlag.String | DreamValue.DreamValueTypeFlag.DreamProc)] [DreamProcParameter("start", DefaultValue = 1, Type = DreamValue.DreamValueTypeFlag.Float)] // BYOND docs say these are uppercase, they're not [DreamProcParameter("end", DefaultValue = 0, Type = DreamValue.DreamValueTypeFlag.Float)] - public static async Task NativeProc_Replace(AsyncNativeProc.State state) { - DreamValue haystack = state.GetArgument(0, "haystack"); - DreamValue replacement = state.GetArgument(1, "replacement"); - int start = state.GetArgument(2, "start").GetValueAsInteger(); - int end = state.GetArgument(3, "end").GetValueAsInteger(); + public static DreamValue NativeProc_Replace(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + DreamValue haystack = bundle.GetArgument(0, "haystack"); + DreamValue replacement = bundle.GetArgument(1, "replacement"); + int start = bundle.GetArgument(2, "start").GetValueAsInteger(); + int end = bundle.GetArgument(3, "end").GetValueAsInteger(); - return await RegexReplace(state, state.Src, haystack, replacement, start, end); + return RegexReplace(src, haystack, replacement, start, end); } private static int GetNext(DreamObject regexInstance, DreamValue startParam, bool isGlobal, string haystackString) { diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 405491c8f5..8c24545377 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -156,10 +156,11 @@ public static DreamValue NativeProc_animate(NativeProc.Bundle bundle, DreamObjec return DreamValue.Null; chainAnim = true; } + + bundle.LastAnimatedObject = new DreamValue(obj); if(obj.IsSubtypeOf(bundle.ObjectTree.Filter)) {//TODO animate filters return DreamValue.Null; } - bundle.LastAnimatedObject = new DreamValue(obj); // TODO: Is this the correct behavior for invalid time? if (!bundle.GetArgument(1, "time").TryGetValueAsFloat(out float time)) return DreamValue.Null; @@ -2048,16 +2049,16 @@ public static DreamValue NativeProc_regex(NativeProc.Bundle bundle, DreamObject? [DreamProcParameter("Replacement", Type = DreamValueTypeFlag.String)] [DreamProcParameter("Start", Type = DreamValueTypeFlag.Float, DefaultValue = 1)] [DreamProcParameter("End", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] - public static async Task NativeProc_replacetext(AsyncNativeProc.State state) { - DreamValue haystack = state.GetArgument(0, "Haystack"); - DreamValue needle = state.GetArgument(1, "Needle"); - DreamValue replacementArg = state.GetArgument(2, "Replacement"); - state.GetArgument(3, "Start").TryGetValueAsInteger(out var start); //1-indexed - int end = state.GetArgument(4, "End").GetValueAsInteger(); //1-indexed + public static DreamValue NativeProc_replacetext(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + DreamValue haystack = bundle.GetArgument(0, "Haystack"); + DreamValue needle = bundle.GetArgument(1, "Needle"); + DreamValue replacementArg = bundle.GetArgument(2, "Replacement"); + bundle.GetArgument(3, "Start").TryGetValueAsInteger(out var start); //1-indexed + int end = bundle.GetArgument(4, "End").GetValueAsInteger(); //1-indexed if (needle.TryGetValueAsDreamObject(out var regexObject)) { // According to the docs, this is the same as /regex.Replace() - return await DreamProcNativeRegex.RegexReplace(state, regexObject, haystack, replacementArg, start, end); + return DreamProcNativeRegex.RegexReplace(regexObject, haystack, replacementArg, start, end); } if (!haystack.TryGetValueAsString(out var text)) { @@ -2955,7 +2956,7 @@ public static DreamValue NativeProc_typesof(NativeProc.Bundle bundle, DreamObjec addingProcs = type.ObjectDefinition.Procs.Values; } else if (typeString.EndsWith("/verb")) { type = bundle.ObjectTree.GetTreeEntry(typeString.Substring(0, typeString.Length - 5)); - addingProcs = type.ObjectDefinition.Verbs; + addingProcs = type.ObjectDefinition.Verbs ?? Enumerable.Empty(); } else { type = bundle.ObjectTree.GetTreeEntry(typeString); } diff --git a/OpenDreamRuntime/Procs/ProcDecoder.cs b/OpenDreamRuntime/Procs/ProcDecoder.cs index 8fc34f8627..2b0ac676f6 100644 --- a/OpenDreamRuntime/Procs/ProcDecoder.cs +++ b/OpenDreamRuntime/Procs/ProcDecoder.cs @@ -56,6 +56,7 @@ public DMReference ReadReference() { case DMReference.Type.Self: return DMReference.Self; case DMReference.Type.Usr: return DMReference.Usr; case DMReference.Type.Args: return DMReference.Args; + case DMReference.Type.World: return DMReference.World; case DMReference.Type.SuperProc: return DMReference.SuperProc; case DMReference.Type.ListIndex: return DMReference.ListIndex; default: throw new Exception($"Invalid reference type {refType}"); @@ -151,6 +152,7 @@ public ITuple DecodeInstruction() { case DreamProcOpcode.CreateTypeEnumerator: case DreamProcOpcode.DestroyEnumerator: case DreamProcOpcode.IsTypeDirect: + case DreamProcOpcode.CreateMultidimensionalList: return (opcode, ReadInt()); case DreamProcOpcode.JumpIfTrueReference: diff --git a/OpenDreamShared/Resources/DMIParser.cs b/OpenDreamShared/Resources/DMIParser.cs index 48c1a66f50..445d68c77f 100644 --- a/OpenDreamShared/Resources/DMIParser.cs +++ b/OpenDreamShared/Resources/DMIParser.cs @@ -213,9 +213,9 @@ public static ParsedDMIDescription ParseDMI(Stream stream) { Vector2u? imageSize = null; while (stream.Position < stream.Length) { - long chunkDataPosition = stream.Position; uint chunkLength = ReadBigEndianUint32(reader); - string chunkType = new string(reader.ReadChars(4)); + string chunkType = Encoding.UTF8.GetString(reader.ReadBytes(4)); + long chunkDataPosition = stream.Position; switch (chunkType) { case "IHDR": //Image header, contains the image size