Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//# issue 2649

// A macro that expands to a path can end up pasted directly after a slash, producing an
// empty path element. BYOND ignores those. This can only happen through macro expansion,
// since a literal "//" in source is a comment.

#define LEADING /datum/first
#define MIDDLE /second
#define TRAILING /first

// "//datum/first"
/LEADING
var/x = 1

// "/datum/first//second"
/datum/first/MIDDLE
var/y = 2

// The shape goonstation's namespace macros expand to, which relies on "##" pasting a
// leading slash onto a trailing one.
#define IDENTITY(_ARGS...) ##_ARGS
#define NS_PATH(_NAME) /datum/namespace/##_NAME
#define NS_PATH_I(_NAME) /datum/namespace/##_NAME/##IDENTITY
#define CREATE_NS(a, b) NS_PATH_I(a)(var/##NS_PATH_I(a##__##b)(##b = NS_PATH(a##__##b)))
#define ADD_TO_NS(a, b) NS_PATH_I(a##__##b)

/datum/namespace
/datum/namespace/RADIO
/datum/namespace/RADIO__COL

CREATE_NS(RADIO, COL)
ADD_TO_NS(RADIO, COL)(var/const/BRIG = "#FF5000")

/proc/RunTest()
var/datum/first/F = new
ASSERT(F.x == 1)

var/datum/first/second/S = new
ASSERT(S.x == 1)
ASSERT(S.y == 2)

// "/datum//first" in an expression
ASSERT(/datum/TRAILING == /datum/first)

var/datum/namespace/RADIO/N = new
ASSERT(N.COL == /datum/namespace/RADIO__COL)
ASSERT(/datum/namespace/RADIO__COL::BRIG == "#FF5000")
30 changes: 28 additions & 2 deletions DMCompiler/Compiler/DM/DMParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -398,12 +398,12 @@ public DMASTFile File() {
if (expression) return null;
}

string? pathElement = PathElement();
string? pathElement = PathElementSkippingEmpty();
if (pathElement != null) {
List<string> pathElements = [pathElement];
bool operatorFlag = false;
while (pathElement != null && Check(TokenType.DM_Slash)) {
pathElement = PathElement();
pathElement = PathElementSkippingEmpty();

if (pathElement != null) {
if(pathElement == "operator") {
Expand Down Expand Up @@ -449,6 +449,32 @@ public DMASTFile File() {
return null;
}

/// <summary>
/// Reads the next path element, ignoring any empty ones (the "//" in "/datum//foo").
/// BYOND accepts these; they can only come from macro expansion because "//" is a comment in source.
/// </summary>
/// <returns>The next path element, or null if there isn't one. Slashes read by a failed attempt are given back.</returns>
private string? PathElementSkippingEmpty() {
string? pathElement = PathElement();
if (pathElement != null || Current().Type != TokenType.DM_Slash)
return pathElement;

List<Token> skippedSlashes = new();
do {
skippedSlashes.Add(Current());
Advance();

pathElement = PathElement();
if (pathElement != null)
return pathElement;
} while (Current().Type == TokenType.DM_Slash);

for (int i = skippedSlashes.Count - 1; i >= 0; i--)
ReuseToken(skippedSlashes[i]);

return null;
}

/// <summary>
/// Extracts the text from this token if it is reasonable for it to appear as a typename in a path.
/// </summary>
Expand Down
Loading