Skip to content

Commit

Permalink
Assume parameter types based on type hints (#1675)
Browse files Browse the repository at this point in the history
  • Loading branch information
wixoaGit authored Feb 18, 2024
1 parent 003f7d2 commit 7b16a79
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 85 deletions.
4 changes: 2 additions & 2 deletions DMCompiler/Compiler/DM/AST/DMAST.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,12 @@ public sealed class DMASTDefinitionParameter(
Location location,
DMASTPath astPath,
DMASTExpression? value,
DMValueType type,
DMValueType? type,
DMASTExpression possibleValues) : DMASTNode(location) {
public DreamPath? ObjectType => _paramDecl.IsList ? DreamPath.List : _paramDecl.TypePath;
public string Name => _paramDecl.VarName;
public DMASTExpression? Value = value;
public readonly DMValueType Type = type;
public readonly DMValueType? Type = type;
public DMASTExpression PossibleValues = possibleValues;

private readonly ProcParameterDeclInfo _paramDecl = new(astPath.Path);
Expand Down
9 changes: 4 additions & 5 deletions DMCompiler/Compiler/DM/DMParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1631,13 +1631,12 @@ public List<DMASTDefinitionParameter> DefinitionParameters(out bool wasIndetermi
BracketWhitespace();
parameter = DefinitionParameter(out wasIndeterminate);

if (parameter != null)
{
if (parameter != null) {
parameters.Add(parameter);
BracketWhitespace();

}
if (Check(TokenType.DM_Null)){

if (Check(TokenType.DM_Null)) {
// Breaking change - BYOND creates a var named null that overrides the keyword. No error.
if (Error(WarningCode.SoftReservedKeyword, "'null' is not a valid variable name")) { // If it's an error, skip over this var instantiation.
Advance();
Expand Down Expand Up @@ -1672,7 +1671,7 @@ public List<DMASTDefinitionParameter> DefinitionParameters(out bool wasIndetermi
value = Expression();
}

var type = AsTypes() ?? DMValueType.Anything;
var type = AsTypes();
Whitespace();

if (Check(TokenType.DM_In)) {
Expand Down
11 changes: 11 additions & 0 deletions DMCompiler/DM/DMObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,15 @@ public bool IsSubtypeOf(DreamPath path) {
if (Parent != null) return Parent.IsSubtypeOf(path);
return false;
}

public DMValueType GetDMValueType() {
if (IsSubtypeOf(DreamPath.Mob))
return DMValueType.Mob;
if (IsSubtypeOf(DreamPath.Obj))
return DMValueType.Obj;
if (IsSubtypeOf(DreamPath.Area))
return DMValueType.Area;

return DMValueType.Anything;
}
}
83 changes: 46 additions & 37 deletions DMCompiler/DM/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,24 @@

namespace DMCompiler.DM {
internal sealed class DMProc {
public class LocalVariable(string name, int id, bool isParameter, DreamPath? type) {
public class LocalVariable(string name, int id, bool isParameter, DreamPath? type, DMValueType? explicitValueType) {
public readonly string Name = name;
public readonly int Id = id;
public bool IsParameter = isParameter;
public readonly bool IsParameter = isParameter;
public DreamPath? Type = type;

/// <summary>
/// The explicit <see cref="DMValueType"/> for this variable
/// <code>var/parameter as mob</code>
/// </summary>
public DMValueType? ExplicitValueType = explicitValueType;
}

public sealed class LocalConstVariable(string name, int id, DreamPath? type, Expressions.Constant value)
: LocalVariable(name, id, false, type) {
: LocalVariable(name, id, false, type, null) {
public readonly Expressions.Constant Value = value;
}

private struct CodeLabelReference(string identifier, string placeholder, Location location, DMProcScope scope) {
public readonly string Identifier = identifier;
public readonly string Placeholder = placeholder;
public readonly Location Location = location;
public readonly DMProcScope Scope = scope;
}

public class CodeLabel {
public readonly int Id;
public readonly string Name;
Expand All @@ -47,6 +46,13 @@ public CodeLabel(string name, long offset) {
}
}

private struct CodeLabelReference(string identifier, string placeholder, Location location, DMProcScope scope) {
public readonly string Identifier = identifier;
public readonly string Placeholder = placeholder;
public readonly Location Location = location;
public readonly DMProcScope Scope = scope;
}

private class DMProcScope {
public readonly Dictionary<string, LocalVariable> LocalVariables = new();
public readonly Dictionary<string, CodeLabel> LocalCodeLabels = new();
Expand All @@ -59,37 +65,35 @@ public DMProcScope(DMProcScope? parentScope) {
}
}

public MemoryStream Bytecode = new();
public List<string> Parameters = new();
public List<DMValueType> ParameterTypes = new();
public readonly MemoryStream Bytecode = new();
public Location Location;
public ProcAttributes Attributes;
public bool IsVerb = false;
public string Name => _astDefinition?.Name ?? "<init>";
public int Id;
public Dictionary<string, int> GlobalVariables = new();
public readonly int Id;
public readonly Dictionary<string, int> GlobalVariables = new();

public VerbSrc? VerbSrc;
public string? VerbName;
public string? VerbCategory = string.Empty;
public string? VerbDesc;
public sbyte Invisibility;

private DMObject _dmObject;
private DMASTProcDefinition? _astDefinition;
private BinaryWriter _bytecodeWriter;
private Stack<CodeLabelReference> _pendingLabelReferences = new();
private Dictionary<string, long> _labels = new();
private List<(long Position, string LabelName)> _unresolvedLabels = new();
private Stack<string>? _loopStack = null;
private Stack<DMProcScope> _scopes = new();
private Dictionary<string, LocalVariable> _parameters = new();
private readonly DMObject _dmObject;
private readonly DMASTProcDefinition? _astDefinition;
private readonly BinaryWriter _bytecodeWriter;
private readonly Stack<CodeLabelReference> _pendingLabelReferences = new();
private readonly Dictionary<string, long> _labels = new();
private readonly List<(long Position, string LabelName)> _unresolvedLabels = new();
private Stack<string>? _loopStack;
private readonly Stack<DMProcScope> _scopes = new();
private readonly Dictionary<string, LocalVariable> _parameters = new();
private int _labelIdCounter;
private int _maxStackSize;
private int _currentStackSize;
private bool _negativeStackSizeError;

private List<LocalVariableJson> _localVariableNames = new();
private readonly List<LocalVariableJson> _localVariableNames = new();
private int _localVariableIdCounter;

private readonly List<SourceInfoJson> _sourceInfo = new();
Expand Down Expand Up @@ -156,15 +160,23 @@ public ProcDefinitionJson GetJsonRepresentation() {
procDefinition.MaxStackSize = _maxStackSize;

if (Bytecode.Length > 0) procDefinition.Bytecode = Bytecode.ToArray();
if (Parameters.Count > 0) {
if (_parameters.Count > 0) {
procDefinition.Arguments = new List<ProcArgumentJson>();

for (int i = 0; i < Parameters.Count; i++) {
string argumentName = Parameters[i];
DMValueType argumentType = ParameterTypes[i];
foreach (var parameter in _parameters.Values) {
if (parameter.ExplicitValueType is not { } argumentType) {
// If no "as" was used then we assume its type based on the type hint
if (parameter.Type is not { } typePath) {
argumentType = DMValueType.Anything;
} else {
var type = DMObjectTree.GetDMObject(typePath, false);

argumentType = type?.GetDMValueType() ?? DMValueType.Anything;
}
}

procDefinition.Arguments.Add(new ProcArgumentJson() {
Name = argumentName,
procDefinition.Arguments.Add(new ProcArgumentJson {
Name = parameter.Name,
Type = argumentType
});
}
Expand Down Expand Up @@ -201,14 +213,11 @@ public DMVariable CreateGlobalVariable(DreamPath? type, string name, bool isCons
return null;
}

public void AddParameter(string name, DMValueType valueType, DreamPath? type) {
Parameters.Add(name);
ParameterTypes.Add(valueType);

public void AddParameter(string name, DMValueType? valueType, DreamPath? type) {
if (_parameters.ContainsKey(name)) {
DMCompiler.Emit(WarningCode.DuplicateVariable, _astDefinition.Location, $"Duplicate argument \"{name}\"");
} else {
_parameters.Add(name, new LocalVariable(name, _parameters.Count, true, type));
_parameters.Add(name, new LocalVariable(name, _parameters.Count, true, type, valueType));
}
}

Expand Down Expand Up @@ -301,7 +310,7 @@ public bool TryAddLocalVariable(string name, DreamPath? type) {
return false;

int localVarId = AllocLocalVariable(name);
return _scopes.Peek().LocalVariables.TryAdd(name, new LocalVariable(name, localVarId, false, type));
return _scopes.Peek().LocalVariables.TryAdd(name, new LocalVariable(name, localVarId, false, type, null));
}

public bool TryAddLocalConstVariable(string name, DreamPath? type, Expressions.Constant value) {
Expand Down
34 changes: 18 additions & 16 deletions OpenDreamClient/ClientVerbSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,7 @@ public IEnumerable<VerbInfo> GetAllVerbs() {
continue;

break;
default:
// TODO: All the other kinds
break;
// TODO: All the other kinds
}

yield return (verbId, src, verb);
Expand Down Expand Up @@ -152,22 +150,26 @@ is VerbAccessibility.InRange or VerbAccessibility.InORange
continue;
}

switch (target.Type) {
case ClientObjectReference.RefType.Entity:
var entity = _entityManager.GetEntity(target.Entity);
var isMob = _entityManager.HasComponent<DreamMobSightComponent>(entity);
if (targetType == DreamValueType.Anything) {
yield return verb;
} else {
switch (target.Type) {
case ClientObjectReference.RefType.Entity:
var entity = _entityManager.GetEntity(target.Entity);
var isMob = _entityManager.HasComponent<DreamMobSightComponent>(entity);

if ((targetType & DreamValueType.Mob) != 0x0 && isMob)
yield return verb;
if ((targetType & DreamValueType.Obj) != 0x0 && !isMob)
yield return verb;
if ((targetType & DreamValueType.Mob) != 0x0 && isMob)
yield return verb;
if ((targetType & DreamValueType.Obj) != 0x0 && !isMob)
yield return verb;

break;
case ClientObjectReference.RefType.Turf:
if ((targetType & DreamValueType.Turf) != 0x0)
yield return verb;
break;
case ClientObjectReference.RefType.Turf:
if ((targetType & DreamValueType.Turf) != 0x0)
yield return verb;

break;
break;
}
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions OpenDreamClient/Input/ContextMenu/VerbMenuPopup.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Linq;
using OpenDreamClient.Interface.Controls;
using OpenDreamClient.Rendering;
using OpenDreamShared.Dream;
using Robust.Client.AutoGenerated;
Expand Down Expand Up @@ -30,11 +32,13 @@ public VerbMenuPopup(ClientVerbSystem? verbSystem, sbyte seeInvisible, ClientObj
}

if (verbSystem != null) {
foreach (var verb in verbSystem.GetExecutableVerbs(_target)) {
if (verb.VerbInfo.IsHidden(false, seeInvisible))
var sorted = verbSystem.GetExecutableVerbs(_target).Order(VerbNameComparer.OrdinalInstance);

foreach (var (verbId, verbSrc, verbInfo) in sorted) {
if (verbInfo.IsHidden(false, seeInvisible))
continue;

AddVerb(verb.Id, verb.Src, verb.VerbInfo);
AddVerb(verbId, verbSrc, verbInfo);
}
}
}
Expand Down
23 changes: 11 additions & 12 deletions OpenDreamRuntime/DreamConnection.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Threading.Tasks;
using System.Web;
using DMCompiler.DM;
using OpenDreamRuntime.Objects;
using OpenDreamRuntime.Objects.Types;
using OpenDreamRuntime.Procs.Native;
Expand Down Expand Up @@ -469,26 +468,26 @@ public void SendFile(DreamResource file, string suggestedName) {
}

public bool TryConvertPromptResponse(DreamValueType type, object? value, out DreamValue converted) {
if (type.HasFlag(DreamValueType.Null) && value == null) {
bool CanBe(DreamValueType canBeType) => (type == DreamValueType.Anything) || ((type & canBeType) != 0x0);

if (CanBe(DreamValueType.Null) && value == null) {
converted = DreamValue.Null;
return true;
} else if (type.HasFlag(DreamValueType.Text) || type.HasFlag(DreamValueType.Message)) {
if (value is string strVal) {
converted = new(strVal);
return true;
}
} else if (type.HasFlag(DreamValueType.Num) && value is float numVal) {
} else if (CanBe(DreamValueType.Text | DreamValueType.Message) && value is string strVal) {
converted = new(strVal);
return true;
} else if (CanBe(DreamValueType.Num) && value is float numVal) {
converted = new DreamValue(numVal);
return true;
} else if (type.HasFlag(DreamValueType.Color) && value is Color colorVal) {
} else if (CanBe(DreamValueType.Color) && value is Color colorVal) {
converted = new DreamValue(colorVal.ToHexNoAlpha());
return true;
} else if ((type & DreamValueType.AllAtomTypes) != 0x0 && value is ClientObjectReference clientRef) {
} else if (CanBe(type & DreamValueType.AllAtomTypes) && value is ClientObjectReference clientRef) {
var atom = _dreamManager.GetFromClientReference(this, clientRef);

if (atom != null) {
if ((atom.IsSubtypeOf(_objectTree.Obj) && !type.HasFlag(DreamValueType.Obj)) ||
(atom.IsSubtypeOf(_objectTree.Mob) && !type.HasFlag(DreamValueType.Mob))) {
if ((atom.IsSubtypeOf(_objectTree.Obj) && !CanBe(DreamValueType.Obj)) ||
(atom.IsSubtypeOf(_objectTree.Mob) && !CanBe(DreamValueType.Mob))) {
converted = default;
return false;
}
Expand Down
4 changes: 2 additions & 2 deletions OpenDreamRuntime/Procs/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ private bool CheckIfNullProc() {
}
}

private static List<DreamValueType> GetArgumentTypes(ProcDefinitionJson json) {
private static List<DreamValueType>? GetArgumentTypes(ProcDefinitionJson json) {
if (json.Arguments == null) {
return new();
return null;
} else {
var argumentTypes = new List<DreamValueType>(json.Arguments.Count);
argumentTypes.AddRange(json.Arguments.Select(a => (DreamValueType)a.Type));
Expand Down
12 changes: 7 additions & 5 deletions OpenDreamRuntime/ServerVerbSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,16 +133,18 @@ private void OnVerbExecuted(ExecuteVerbEvent msg, EntitySessionEventArgs args) {
var src = _dreamManager.GetFromClientReference(connection, msg.Src);
if (src == null || !_verbIdToProc.TryGetValue(msg.VerbId, out var verb) || !CanExecute(connection, src, verb))
return;
if (msg.Arguments.Length != verb.ArgumentTypes?.Count) {

var argCount = verb.ArgumentTypes?.Count ?? 0;
if (msg.Arguments.Length != argCount) {
_sawmill.Error(
$"User \"{args.SenderSession.Name}\" gave {msg.Arguments.Length} argument(s) to the \"{verb.Name}\" verb which only has {verb.ArgumentTypes?.Count} argument(s)");
$"User \"{args.SenderSession.Name}\" gave {msg.Arguments.Length} argument(s) to the \"{verb.Name}\" verb which only has {argCount} argument(s)");
return;
}

// Convert the values the client gave to DreamValues
DreamValue[] arguments = new DreamValue[verb.ArgumentTypes.Count];
for (int i = 0; i < verb.ArgumentTypes.Count; i++) {
var argType = verb.ArgumentTypes[i];
DreamValue[] arguments = new DreamValue[argCount];
for (int i = 0; i < argCount; i++) {
var argType = verb.ArgumentTypes![i];

if (!connection.TryConvertPromptResponse(argType, msg.Arguments[i], out arguments[i])) {
_sawmill.Error(
Expand Down
4 changes: 1 addition & 3 deletions OpenDreamShared/Dream/VerbSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ public bool IsHidden(bool ignoreHiddenAttr, sbyte seeInvisibility) =>
// If the verb's first argument is an atom type, it takes that type as a target
[Pure]
public DreamValueType? GetTargetType() =>
(Arguments.Length != 0 && (Arguments[0].Types & DreamValueType.AllAtomTypes) != 0x0)
? Arguments[0].Types
: null;
(Arguments.Length != 0) ? Arguments[0].Types : null;

public override string ToString() => GetCommandName();
}
Expand Down

0 comments on commit 7b16a79

Please sign in to comment.