Skip to content

Commit

Permalink
Merge branch 'master' into particles_od
Browse files Browse the repository at this point in the history
  • Loading branch information
amylizzle authored Feb 24, 2025
2 parents 007360d + b7aaab6 commit 6587932
Show file tree
Hide file tree
Showing 19 changed files with 1,179 additions and 1,099 deletions.
19 changes: 14 additions & 5 deletions Content.Tests/DMProject/Tests/Operators/scope.dm
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/datum
var/static/datum/three/three
var/text = "hello"

var/static/one = "one"

/datum/three
var/static/datum/four/four
text = "hi"
var/overridden_text = type::text
var/original_text = parent_type::text

/datum/three/proc/typetest()
// initial shorthand, type:: and parent_type::
ASSERT(text == "hi")
Expand All @@ -21,7 +21,7 @@ var/static/one = "one"
ASSERT(src::text == "hi")
ASSERT(src::overridden_text == "hi")
ASSERT(src::original_text == "hello")

// proc reference
ASSERT(__PROC__ == /datum/three::typetest())

Expand All @@ -31,11 +31,17 @@ var/static/one = "one"
/datum/five
var/static/six = "three four five six"

/datum/six
var/toughness = 100
reinforced
toughness = parent_type::toughness + 50

/proc/return_two()
return "two"

/proc/RunTest()
// global vars and procs
var/one = "not one"
ASSERT(::one == "one")
ASSERT(::return_two() == "two")

Expand All @@ -52,10 +58,13 @@ var/static/one = "one"
ASSERT(test::three::four.five::six == "3 4 5 6")
ASSERT(/datum::three::four.five::six == "3 4 5 6")

// this does not compile in BYOND, that is just a bug
// this does not compile in BYOND, that is just a bug
test::three::four.five::six = "7 8 9 10"
ASSERT(test::three::four.five::six == "7 8 9 10")
ASSERT(/datum::three::four.five::six == "7 8 9 10")

var/datum/three/threetest = new
threetest.typetest()

var/datum/six/reinforced/fourtest = new()
ASSERT(fourtest.toughness == 150)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// COMPILE ERROR OD0011

/datum/armor
var/toughness = 100
reinforced
proc/test_proc()
toughness = parent_type::toughness + 50
1 change: 1 addition & 0 deletions DMCompiler/Compiler/CompilerError.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public enum WarningCode {

// 2000 - 2999 are reserved for compiler configuration of actual behaviour.
SoftReservedKeyword = 2000, // For keywords that SHOULD be reserved, but don't have to be. 'null' and 'defined', for instance
ScopeOperandNamedType = 2001, // Scope operator is used on a var named type or parent_type, maybe unintentionally
DuplicateVariable = 2100,
DuplicateProcDefinition = 2101,
PointlessParentCall = 2205,
Expand Down
17 changes: 12 additions & 5 deletions DMCompiler/DM/Builders/DMExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -644,11 +644,18 @@ private DMExpression BuildScopeIdentifier(DMASTScopeIdentifier scopeIdentifier,
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 (scopeMode is Normal && ctx.Proc != null)
return BadExpression(WarningCode.BadExpression, identifier.Location,
"Use of \"type::\" and \"parent_type::\" outside of a context is forbidden");

if (identifier.Identifier == "parent_type") {
if (scopeMode is Normal && ctx.Proc != null) {
if (ctx.Proc.GetLocalVariable(identifier.Identifier) != null) {
// actually - it's referring to a local variable named "type" or "parent_type"... just do the usual thing
Compiler.Emit(WarningCode.ScopeOperandNamedType, identifier.Location,
$"Using scope operator :: on a variable named \"type\" or \"parent_type\" is ambiguous. Consider changing the variable name from \"{identifier.Identifier}\".");
expression = BuildExpression(scopeIdentifier.Expression, inferredPath);
} else {
return BadExpression(WarningCode.BadExpression, identifier.Location,
"Use of \"type::\" and \"parent_type::\" inside an object proc is only valid when " +
"there is a local variable named \"type\" or \"parent_type\"");
}
} else if (identifier.Identifier == "parent_type") {
if (ctx.Type.Parent == null)
return BadExpression(WarningCode.ItemDoesntExist, identifier.Location,
$"Type {ctx.Type.Path} does not have a parent");
Expand Down
70 changes: 49 additions & 21 deletions DMCompiler/DM/Builders/DMProcBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -602,37 +602,30 @@ public void ProcessLoopAssignment(LValue lValue) {
}
}

public void ProcessStatementForList(DMExpression list, DMExpression outputVar, DMComplexValueType? dmTypes, DMASTProcBlockInner body) {
public void ProcessStatementForList(DMExpression list, DMExpression outputVar, DMComplexValueType? typeCheck, DMASTProcBlockInner body) {
if (outputVar is not LValue lValue) {
compiler.Emit(WarningCode.BadExpression, outputVar.Location, "Invalid output var");
lValue = null;
lValue = new BadLValue(outputVar.Location);
}

// Depending on the var's type and possibly a given "as [types]", an implicit istype() check is performed
DreamPath? implicitTypeCheck = null;
if (dmTypes == null) {
// No "as" means the var's type will be used
implicitTypeCheck = lValue?.Path;
} else if (dmTypes.Value.TypePath != null) {
// "as /datum" will perform a check for /datum
implicitTypeCheck = dmTypes.Value.TypePath;
} else if (!dmTypes.Value.IsAnything) {
// "as anything" performs no check. Other values are unimplemented.
compiler.UnimplementedWarning(outputVar.Location,
$"As type {dmTypes} in for loops is unimplemented. No type check will be performed.");
// Having no "as [types]" will use the var's type for the type filter
if (typeCheck == null && lValue.Path != null) {
typeCheck = lValue.Path;
}

bool performingImplicitIsType = false;
list.EmitPushValue(ExprContext);
if (implicitTypeCheck != null) {
if (compiler.DMObjectTree.TryGetTypeId(implicitTypeCheck.Value, out var filterTypeId)) {
if (typeCheck?.TypePath is { } typeCheckPath) { // We have a specific type to filter for
if (compiler.DMObjectTree.TryGetTypeId(typeCheckPath, out var filterTypeId)) {
// Create an enumerator that will do the implicit istype() for us
proc.CreateFilteredListEnumerator(filterTypeId, implicitTypeCheck.Value);
proc.CreateFilteredListEnumerator(filterTypeId, typeCheckPath);
} else {
compiler.Emit(WarningCode.ItemDoesntExist, outputVar.Location,
$"Cannot filter enumeration by type {implicitTypeCheck.Value}, it does not exist");
$"Cannot filter enumeration by type {typeCheckPath}, it does not exist");
proc.CreateListEnumerator();
}
} else {
} else { // Either no type filter or we're using the slower "as [types]"
performingImplicitIsType = !(typeCheck is null || typeCheck.Value.IsAnything);
proc.CreateListEnumerator();
}

Expand All @@ -643,8 +636,43 @@ public void ProcessStatementForList(DMExpression list, DMExpression outputVar, D
{
proc.MarkLoopContinue(loopLabel);

if (lValue != null) {
ProcessLoopAssignment(lValue);
ProcessLoopAssignment(lValue);

// "as mob|etc" will insert code equivalent to "if(!(istype(X, mob) || istype(X, etc))) continue;"
// It would be ideal if the type filtering could be done by the interpreter, like it does when the var has a type
// But the code currently isn't structured in a way that it could be done nicely
if (performingImplicitIsType) {
var afterTypeCheckIf = proc.NewLabelName();
var afterTypeCheckExpr = proc.NewLabelName();

void CheckType(DMValueType type, DreamPath path, ref bool doOr) {
if (!typeCheck!.Value.Type.HasFlag(type))
return;
if (!compiler.DMObjectTree.TryGetTypeId(path, out var typeId))
return;

if (doOr)
proc.BooleanOr(afterTypeCheckExpr);
doOr = true;

lValue.EmitPushValue(ExprContext);
proc.PushType(typeId);
proc.IsType();
}

bool doOr = false; // Only insert BooleanOr after the first type
CheckType(DMValueType.Area, DreamPath.Area, ref doOr);
CheckType(DMValueType.Turf, DreamPath.Turf, ref doOr);
CheckType(DMValueType.Obj, DreamPath.Obj, ref doOr);
CheckType(DMValueType.Mob, DreamPath.Mob, ref doOr);
proc.AddLabel(afterTypeCheckExpr);
if (doOr) {
proc.Not();
proc.JumpIfFalse(afterTypeCheckIf);
proc.Continue();
}

proc.AddLabel(afterTypeCheckIf);
}

ProcessBlockInner(body);
Expand Down
5 changes: 0 additions & 5 deletions DMCompiler/DM/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -579,11 +579,6 @@ public void Pop() {
WriteOpcode(DreamProcOpcode.Pop);
}

public void PopReference(DMReference reference) {
WriteOpcode(DreamProcOpcode.PopReference);
WriteReference(reference, false);
}

public void BooleanOr(string endLabel) {
WriteOpcode(DreamProcOpcode.BooleanOr);
WriteLabel(endLabel);
Expand Down
13 changes: 13 additions & 0 deletions DMCompiler/DM/Expressions/LValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ public virtual void EmitPushInitial(ExpressionContext ctx) {
}
}

/// <summary>
/// Used when there was an error regarding L-Values
/// </summary>
/// <remarks>Emit an error code before creating!</remarks>
internal sealed class BadLValue(Location location) : LValue(location, null) {
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
ctx.Proc.PushString("Encountered a bad LValue (compiler bug!)");
ctx.Proc.Throw();
}
}

// global
internal class Global(Location location) : LValue(location, null) {
public override DMReference EmitReference(ExpressionContext ctx, string endLabel,
Expand Down
1 change: 1 addition & 0 deletions DMCompiler/DMStandard/DefaultPragmaConfig.dm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

//2000-2999
#pragma SoftReservedKeyword error
#pragma ScopeOperandNamedType warning
#pragma DuplicateVariable warning
#pragma DuplicateProcDefinition error
#pragma PointlessParentCall warning
Expand Down
3 changes: 3 additions & 0 deletions DMCompiler/DMStandard/Types/Atoms/_Atom.dm
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
var/pixel_y = 0
var/pixel_z = 0
var/pixel_w = 0

var/icon_w = 0 as opendream_unimplemented
var/icon_z = 0 as opendream_unimplemented

var/icon = null
var/icon_state = ""
Expand Down
4 changes: 4 additions & 0 deletions OpenDreamClient/Interface/Controls/ControlWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public ControlWindow(WindowDescriptor windowDescriptor) : base(windowDescriptor,
IoCManager.InjectDependencies(this);
}

public IClydeWindow? GetClydeWindow() {
return _myWindow.osWindow is null ? _myWindow.clydeWindow : _myWindow.osWindow.ClydeWindow;
}

protected override void UpdateElementDescriptor() {
// Don't call base.UpdateElementDescriptor();

Expand Down
4 changes: 2 additions & 2 deletions OpenDreamClient/Interface/DebugWindows/MacrosWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ private GridContainer CreateMacroTable(InterfaceMacroSet macroSet) {

foreach (var macro in macroSet.Macros.Values) {
var idText = macro.Id;
if (macro.ElementDescriptor.Name.Value != idText.Value)
idText.Value += $" ({macro.ElementDescriptor.Name.AsRaw()})";
if (macro.ElementDescriptor.Id.Value != idText.Value)
idText.Value += $" ({macro.ElementDescriptor.Id.AsRaw()})";

var idLabel = new Label { Text = idText.AsRaw() };
var commandLabel = new Label { Text = macro.Command };
Expand Down
25 changes: 25 additions & 0 deletions OpenDreamClient/Interface/DreamInterfaceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using System.Linq;
using Robust.Shared.Map;

namespace OpenDreamClient.Interface;

Expand Down Expand Up @@ -55,6 +56,7 @@ internal sealed class DreamInterfaceManager : IDreamInterfaceManager {
public Dictionary<string, ControlWindow> Windows { get; } = new();
public Dictionary<string, InterfaceMenu> Menus { get; } = new();
public Dictionary<string, InterfaceMacroSet> MacroSets { get; } = new();
private Dictionary<WindowId, ControlWindow> ClydeWindowIdToControl { get; } = new();

public ViewRange View {
get => _view;
Expand Down Expand Up @@ -124,6 +126,7 @@ public void Initialize() {
_netManager.RegisterNetMessage<MsgLoadInterface>(RxLoadInterface);
_netManager.RegisterNetMessage<MsgAckLoadInterface>();
_netManager.RegisterNetMessage<MsgUpdateClientInfo>(RxUpdateClientInfo);
_clyde.OnWindowFocused += OnWindowFocused;
}

private void RxUpdateStatPanels(MsgUpdateStatPanels message) {
Expand Down Expand Up @@ -914,6 +917,24 @@ private void LoadInterface(InterfaceDescriptor descriptor) {
LayoutContainer.SetAnchorBottom(DefaultWindow.UIElement, 1);

_uiManager.StateRoot.AddChild(DefaultWindow.UIElement);

if (DefaultWindow.GetClydeWindow() is { } clydeWindow) {
ClydeWindowIdToControl.Add(clydeWindow.Id, DefaultWindow);
}
}

private void OnWindowFocused(WindowFocusedEventArgs args) {
if(ClydeWindowIdToControl.TryGetValue(args.Window.Id, out var controlWindow)){
_sawmill.Debug($"window id {controlWindow.Id} was {(args.Focused ? "focused" : "defocused")}");
WindowDescriptor descriptor = (WindowDescriptor) controlWindow.ElementDescriptor;
descriptor.Focus = new DMFPropertyBool(args.Focused);
if(args.Focused && MacroSets.TryGetValue(descriptor.Macro.AsRaw(), out var windowMacroSet)){
_sawmill.Debug($"Activating macroset {descriptor.Macro}");
windowMacroSet.SetActive();
}
}
else
_sawmill.Debug($"window id was not found (probably a modal) but was {(args.Focused ? "focused" : "defocused")}");
}

private void LoadDescriptor(ElementDescriptor descriptor) {
Expand All @@ -936,6 +957,10 @@ private void LoadDescriptor(ElementDescriptor descriptor) {
DefaultWindow = window;
}

if (window.GetClydeWindow() is { } clydeWindow) {
ClydeWindowIdToControl.Add(clydeWindow.Id, window);
}

break;
}
}
Expand Down
13 changes: 9 additions & 4 deletions OpenDreamClient/Interface/Html/HtmlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,13 @@ void PushCurrentText() {
string tagType = attributes[0].ToLowerInvariant();

currentText.Clear();
bool isSelfClosing = IsSelfClosing(tagType, attributes);
if (closingTag) {
if (tags.Count == 0) {
if (isSelfClosing) {
// ignore closing tags of void elements since they don't
// do anything anyway. Should probably warn.
return;
} else if (tags.Count == 0) {
Sawmill.Error("Unexpected closing tag");
return;
} else if (tags.Peek() != tagType) {
Expand All @@ -82,9 +87,9 @@ void PushCurrentText() {
appendTo.Pop();
tags.Pop();
} else {
tags.Push(tagType);

bool isSelfClosing = IsSelfClosing(tagType, attributes);
if (!isSelfClosing) {
tags.Push(tagType);
}
appendTo.PushTag(new MarkupNode(tagType, null, ParseAttributes(attributes)), selfClosing: isSelfClosing);
}

Expand Down
4 changes: 2 additions & 2 deletions OpenDreamClient/Interface/InterfaceMacro.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public InterfaceMacroSet(MacroSetDescriptor descriptor, IEntitySystemManager ent
_entitySystemManager = entitySystemManager;
_uiManager = uiManager;

_inputContextName = $"{InputContextPrefix}{ElementDescriptor.Name}";
_inputContextName = $"{InputContextPrefix}{ElementDescriptor.Id}";
if (inputManager.Contexts.TryGetContext(_inputContextName, out var existingContext)) {
_inputContext = existingContext;
} else {
Expand All @@ -45,7 +45,7 @@ public override void AddChild(ElementDescriptor descriptor) {
}

public void SetActive() {
_inputManager.Contexts.SetActiveContext($"{InputContextPrefix}{ElementDescriptor.Name}");
_inputManager.Contexts.SetActiveContext($"{InputContextPrefix}{ElementDescriptor.Id}");
}
}

Expand Down
Loading

0 comments on commit 6587932

Please sign in to comment.