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
🆑
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
/🆑
This commit is contained in:
SyncIt21
2025-09-07 11:10:51 +02:00
committed by GitHub
parent 5fe727b6e1
commit 04b667b7ba
2 changed files with 47 additions and 13 deletions
+46 -12
View File
@@ -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))