Skip to content

Commit cc7e807

Browse files
committed
Handle brackets inside string arguments in split_args
split_args treated every bracket and quote as structural, so a closing bracket inside a quoted string argument (e.g. reason="closed ]" or an emoticon "Hello :)") raised ValueError and crashed Colang flow parsing. Track string state so that while inside a quoted string only the matching closing quote is special and brackets, commas and the other quote type are literal, matching the sibling splitters (word_split, char_split, params_tokenize). Signed-off-by: winklemad <winklemad@outlook.com>
1 parent 5331054 commit cc7e807

2 files changed

Lines changed: 35 additions & 2 deletions

File tree

nemoguardrails/colang/v1_0/lang/utils.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,21 @@ def split_args(args_str: str) -> List[str]:
5050
closing_char = {"[": "]", "(": ")", "{": "}", "'": "'", '"': '"'}
5151

5252
for char in args_str:
53+
# While inside a string literal, only the matching closing quote is
54+
# special; brackets, commas and the other quote type are literal text.
55+
if stack and stack[-1] in "\"'":
56+
if char == stack[-1]:
57+
stack.pop()
58+
current.append(char)
59+
continue
60+
5361
if char in "([{":
5462
stack.append(char)
5563
current.append(char)
56-
elif char in "\"'" and (len(stack) == 0 or stack[-1] != char):
64+
elif char in "\"'":
5765
stack.append(char)
5866
current.append(char)
59-
elif char in ")]}\"'":
67+
elif char in ")]}":
6068
if not stack:
6169
raise ValueError(f"Invalid syntax for string: {args_str}; unexpected closing '{char}'")
6270
if char != closing_char[stack[-1]]:

tests/test_parser_utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,28 @@ def test_split_args_unbalanced_closing_bracket_raises_value_error(args_str):
5555
# the ValueError the function uses to report invalid syntax.
5656
with pytest.raises(ValueError):
5757
split_args(args_str)
58+
59+
60+
@pytest.mark.parametrize(
61+
"args_str, expected",
62+
[
63+
# A closing bracket inside a quoted string is literal text, not structural.
64+
('x="]"', ['x="]"']),
65+
('x=")"', ['x=")"']),
66+
('x="}"', ['x="}"']),
67+
# Real-world strings: an emoticon or a citation marker.
68+
('msg="Hello :)"', ['msg="Hello :)"']),
69+
('note="cite [1] and (2)"', ['note="cite [1] and (2)"']),
70+
('reason="closed ]"', ['reason="closed ]"']),
71+
# A comma inside a string does not split the arguments.
72+
('x="a, b"', ['x="a, b"']),
73+
# The other quote character inside a string is literal too.
74+
('x="it\'s ok)"', ['x="it\'s ok)"']),
75+
# A bracket-in-string argument alongside a normal argument still splits.
76+
('n=1, x="]"', ["n=1", 'x="]"']),
77+
],
78+
)
79+
def test_split_args_bracket_inside_string(args_str, expected):
80+
# Brackets, commas, and the other quote character inside a quoted string
81+
# argument are literal text and must not be treated as structural syntax.
82+
assert split_args(args_str) == expected

0 commit comments

Comments
 (0)