From 04b667b7ba76b69009c3c54693c0d03600cd74e4 Mon Sep 17 00:00:00 2001 From: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:40:51 +0530 Subject: [PATCH] Fixes 4 bugs with `/datum/parsed_map/readlist()` (#92652) ## About The Pull Request - Fixes the proc skipping `0` & `null` values. Text like `"0"` & `"null"` when sent to `parse_constant()` will yield `0` & `null` respectively but when checked against `!` operator https://github.com/tgstation/tgstation/blob/c3e716323e3bbcfdd8704def76791664a34b8adc/code/modules/mapping/reader.dm#L1019 It gets mistaken as an empty value/new line so its skipped So if you had a list such as `list("Hello", 0, "null")` When parsed it would yield `list("Hello")`. That's fixed now. We check the text before parsing it - Fixes `=` symbol getting mistaken for a key/value pair if it is embedded inside a string expression. Consider this list `list("A", "B = C")`. This list should just be linear because the equals symbol is meant to escaped as it is within a string literal. However `findtext()` proc doesn't account for that https://github.com/tgstation/tgstation/blob/c3e716323e3bbcfdd8704def76791664a34b8adc/code/modules/mapping/reader.dm#L1014 So we end up creating a key/value pair and a linear element, creating a broken list like that runtimes(because of the missing `"` symbol in breaking up the string at the middle) The solution is simple, we already have the proc `find_next_delimiter_position()` that will escape everything between `"` so we now use that to find the position of `=` - Fixes the proc being unable to parse alists who's values themselves are lists. Consider the example `list("A" = list(1, 2))` When we look for `,` symbol to check for the next list element here https://github.com/tgstation/tgstation/blob/c3e716323e3bbcfdd8704def76791664a34b8adc/code/modules/mapping/reader.dm#L1015 You get a broken left literal like this `"A" = list(1` because the comma within the list is getting mistaken for a new element. Now we check for such edge cases and remember to parse the full list before checking for the next comma & now your alists can contain lists as elements themselves - Map reader can read nested lists to any degree e.g. `list(list(3, 4), list(5, 6))` parses correctly ## Changelog :cl: fix: the map reader now reads null & 0 values into lists fix: the map reader now parses associative lists(maps/alists) correctly in cases where = sign is embedded within a string fix: the map reader now parses associative lists(maps/alists) who's values themselves can be lists fix: the map reader can now parse nested lists to any degree /:cl: --- _maps/map_files/wawastation/wawastation.dmm | 2 +- code/modules/mapping/reader.dm | 58 ++++++++++++++++----- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm index 997f5f45a51..fcdeca02178 100644 --- a/_maps/map_files/wawastation/wawastation.dmm +++ b/_maps/map_files/wawastation/wawastation.dmm @@ -34581,7 +34581,7 @@ /obj/machinery/elevator_control_panel{ linked_elevator_id = "aisat"; pixel_x = 32; - preset_destination_names = list(2 = "Telecomms", 3 = "AI Core") + preset_destination_names = list("2" = "Telecomms", "3" = "AI Core") }, /turf/open/openspace, /area/station/ai_monitored/turret_protected/aisat_interior) diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm index 1efcf71676e..c32f85af1db 100644 --- a/code/modules/mapping/reader.dm +++ b/code/modules/mapping/reader.dm @@ -1008,27 +1008,61 @@ GLOBAL_LIST_EMPTY(map_model_default) var/old_position = 1 while(position != 0) // find next delimiter that is not within "..." - position = find_next_delimiter_position(text,old_position,delimiter) + position = find_next_delimiter_position(text, old_position, delimiter) // check if this is a simple variable (as in list(var1, var2)) or an associative one (as in list(var1="foo",var2=7)) - var/equal_position = findtext(text,"=",old_position, position) - var/trim_left = trim(copytext(text,old_position,(equal_position ? equal_position : position))) - var/left_constant = parse_constant(trim_left) - if(position) - old_position = position + length(text[position]) - if(!left_constant) // damn newlines man. Exists to provide behavior consistency with the above loop. not a major cost becuase this path is cold + var/equal_position = find_next_delimiter_position(text, old_position, "=") + var/trim_left = trim(copytext(text, old_position, (equal_position ? equal_position : position))) + if(!trim_left) // damn newlines man. Exists to provide behavior consistency with the above loop. not a major cost becuase this path is cold + if(position) + old_position = position + length(text[position]) continue - if(equal_position && !isnum(left_constant)) + var/is_simple = TRUE //linear list + var/trim_right = trim_left //simple var + if(equal_position) // Associative var, so do the association. // Note that numbers cannot be keys - the RHS is dropped if so. - var/trim_right = trim(copytext(text, equal_position + length(text[equal_position]), position)) - var/right_constant = parse_constant(trim_right) - .[left_constant] = right_constant - else // simple var + trim_right = trim(copytext(text, equal_position + length(text[equal_position]), position)) + is_simple = FALSE + + //right value is a list and since we used the delimiter , this text would be incomplete so we need to parse the full string + if(copytext(trim_right, 1, 6) == "list(") + var/start_index = is_simple ? old_position : equal_position + length(text[equal_position]) + var/opening_count = 0 + var/closing_count = 0 + var/index = start_index + var/begin = FALSE + while(!begin || (opening_count != closing_count)) + var/char = text[index] + if(char == "(") + opening_count += 1 + begin = TRUE + else if(char == ")") + closing_count += 1 + index += 1 + trim_right = trim(copytext(text, start_index, index)) + if(is_simple) + trim_left = trim_right + if(index == length(text)) //stops a wasteful iteration when we reach the end + position = 0 + else + old_position = index + 1 //this moves our pointer past , to the next element + else if(position) + old_position = position + length(text[position]) + + //assign value + var/left_constant = parse_constant(trim_left) + if(is_simple) . += list(left_constant) + else + .[left_constant] = parse_constant(trim_right) /datum/parsed_map/proc/parse_constant(text) + // empty text + if(!text) + return "" + // number var/num = text2num(text) if(isnum(num))