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
-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