From 61234ae164ac315326a47d9505e17ce8d5e77fc2 Mon Sep 17 00:00:00 2001 From: invrainbow <77120437+invrainbow@users.noreply.github.com> Date: Mon, 12 Feb 2024 19:30:25 -0800 Subject: [PATCH] Fix operator precedence (#2573) --- reflex/utils/format.py | 17 ++++++- reflex/vars.py | 3 ++ tests/components/layout/test_match.py | 6 +-- tests/components/test_component.py | 2 +- tests/test_var.py | 66 +++++++++++++-------------- 5 files changed, 56 insertions(+), 38 deletions(-) diff --git a/reflex/utils/format.py b/reflex/utils/format.py index 9c67f1fc0..aaa371c2a 100644 --- a/reflex/utils/format.py +++ b/reflex/utils/format.py @@ -52,6 +52,10 @@ def get_close_char(open: str, close: str | None = None) -> str: def is_wrapped(text: str, open: str, close: str | None = None) -> bool: """Check if the given text is wrapped in the given open and close characters. + "(a) + (b)" --> False + "((abc))" --> True + "(abc)" --> True + Args: text: The text to check. open: The open character. @@ -61,7 +65,18 @@ def is_wrapped(text: str, open: str, close: str | None = None) -> bool: Whether the text is wrapped. """ close = get_close_char(open, close) - return text.startswith(open) and text.endswith(close) + if not (text.startswith(open) and text.endswith(close)): + return False + + depth = 0 + for ch in text[:-1]: + if ch == open: + depth += 1 + if ch == close: + depth -= 1 + if depth == 0: # it shouldn't close before the end + return False + return True def wrap( diff --git a/reflex/vars.py b/reflex/vars.py index 98118b1d1..4e605f9d6 100644 --- a/reflex/vars.py +++ b/reflex/vars.py @@ -736,6 +736,9 @@ class Var: left_operand_full_name = get_operand_full_name(left_operand) right_operand_full_name = get_operand_full_name(right_operand) + left_operand_full_name = format.wrap(left_operand_full_name, "(") + right_operand_full_name = format.wrap(right_operand_full_name, "(") + # apply function to operands if fn is not None: if invoke_fn: diff --git a/tests/components/layout/test_match.py b/tests/components/layout/test_match.py index b8be69721..ad9e7cb4e 100644 --- a/tests/components/layout/test_match.py +++ b/tests/components/layout/test_match.py @@ -72,7 +72,7 @@ def test_match_components(): assert fifth_return_value_render["name"] == "RadixThemesText" assert fifth_return_value_render["children"][0]["contents"] == "{`fifth value`}" - assert match_cases[5][0]._var_name == "(match_state.num + 1)" + assert match_cases[5][0]._var_name == "((match_state.num) + (1))" assert match_cases[5][0]._var_type == int fifth_return_value_render = match_cases[5][1].render() assert fifth_return_value_render["name"] == "RadixThemesText" @@ -102,7 +102,7 @@ def test_match_components(): "(() => { switch (JSON.stringify(match_state.value)) {case JSON.stringify(1): return (`first`); break;case JSON.stringify(2): case JSON.stringify(3): return " "(`second value`); break;case JSON.stringify([1, 2]): return (`third-value`); break;case JSON.stringify(`random`): " 'return (`fourth_value`); break;case JSON.stringify({"foo": "bar"}): return (`fifth value`); ' - "break;case JSON.stringify((match_state.num + 1)): return (`sixth value`); break;case JSON.stringify(`${match_state.value} - string`): " + "break;case JSON.stringify(((match_state.num) + (1))): return (`sixth value`); break;case JSON.stringify(`${match_state.value} - string`): " "return (match_state.string); break;case JSON.stringify(match_state.string): return (`${match_state.value} - string`); break;default: " "return (`default value`); break;};})()", ), @@ -121,7 +121,7 @@ def test_match_components(): "(() => { switch (JSON.stringify(match_state.value)) {case JSON.stringify(1): return (`first`); break;case JSON.stringify(2): case JSON.stringify(3): return " "(`second value`); break;case JSON.stringify([1, 2]): return (`third-value`); break;case JSON.stringify(`random`): " 'return (`fourth_value`); break;case JSON.stringify({"foo": "bar"}): return (`fifth value`); ' - "break;case JSON.stringify((match_state.num + 1)): return (`sixth value`); break;case JSON.stringify(`${match_state.value} - string`): " + "break;case JSON.stringify(((match_state.num) + (1))): return (`sixth value`); break;case JSON.stringify(`${match_state.value} - string`): " "return (match_state.string); break;case JSON.stringify(match_state.string): return (`${match_state.value} - string`); break;default: " "return (match_state.string); break;};})()", ), diff --git a/tests/components/test_component.py b/tests/components/test_component.py index b7783e0f8..3c0c09d7e 100644 --- a/tests/components/test_component.py +++ b/tests/components/test_component.py @@ -450,7 +450,7 @@ def test_component_event_trigger_arbitrary_args(): assert comp.render()["props"][0] == ( "onFoo={(__e,_alpha,_bravo,_charlie) => addEvents(" - '[Event("c1_state.mock_handler", {_e:__e.target.value,_bravo:_bravo["nested"],_charlie:(_charlie.custom + 42)})], ' + '[Event("c1_state.mock_handler", {_e:__e.target.value,_bravo:_bravo["nested"],_charlie:((_charlie.custom) + (42))})], ' "(__e,_alpha,_bravo,_charlie), {})}" ) diff --git a/tests/test_var.py b/tests/test_var.py index df9b7453e..3f2f9c4fa 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -245,30 +245,30 @@ def test_basic_operations(TestObj): Args: TestObj: The test object. """ - assert str(v(1) == v(2)) == "{(1 === 2)}" - assert str(v(1) != v(2)) == "{(1 !== 2)}" - assert str(v(1) < v(2)) == "{(1 < 2)}" - assert str(v(1) <= v(2)) == "{(1 <= 2)}" - assert str(v(1) > v(2)) == "{(1 > 2)}" - assert str(v(1) >= v(2)) == "{(1 >= 2)}" - assert str(v(1) + v(2)) == "{(1 + 2)}" - assert str(v(1) - v(2)) == "{(1 - 2)}" - assert str(v(1) * v(2)) == "{(1 * 2)}" - assert str(v(1) / v(2)) == "{(1 / 2)}" - assert str(v(1) // v(2)) == "{Math.floor(1 / 2)}" - assert str(v(1) % v(2)) == "{(1 % 2)}" - assert str(v(1) ** v(2)) == "{Math.pow(1 , 2)}" - assert str(v(1) & v(2)) == "{(1 && 2)}" - assert str(v(1) | v(2)) == "{(1 || 2)}" + assert str(v(1) == v(2)) == "{((1) === (2))}" + assert str(v(1) != v(2)) == "{((1) !== (2))}" + assert str(v(1) < v(2)) == "{((1) < (2))}" + assert str(v(1) <= v(2)) == "{((1) <= (2))}" + assert str(v(1) > v(2)) == "{((1) > (2))}" + assert str(v(1) >= v(2)) == "{((1) >= (2))}" + assert str(v(1) + v(2)) == "{((1) + (2))}" + assert str(v(1) - v(2)) == "{((1) - (2))}" + assert str(v(1) * v(2)) == "{((1) * (2))}" + assert str(v(1) / v(2)) == "{((1) / (2))}" + assert str(v(1) // v(2)) == "{Math.floor((1) / (2))}" + assert str(v(1) % v(2)) == "{((1) % (2))}" + assert str(v(1) ** v(2)) == "{Math.pow((1) , (2))}" + assert str(v(1) & v(2)) == "{((1) && (2))}" + assert str(v(1) | v(2)) == "{((1) || (2))}" assert str(v([1, 2, 3])[v(0)]) == "{[1, 2, 3].at(0)}" assert str(v({"a": 1, "b": 2})["a"]) == '{{"a": 1, "b": 2}["a"]}' - assert str(v("foo") == v("bar")) == '{("foo" === "bar")}' + assert str(v("foo") == v("bar")) == '{(("foo") === ("bar"))}' assert ( str( Var.create("foo", _var_is_local=False) == Var.create("bar", _var_is_local=False) ) - == "{(foo === bar)}" + == "{((foo) === (bar))}" ) assert ( str( @@ -279,7 +279,7 @@ def test_basic_operations(TestObj): _var_name="bar", _var_type=str, _var_is_string=True, _var_is_local=True ) ) - == "(`foo` === `bar`)" + == "((`foo`) === (`bar`))" ) assert ( str( @@ -295,7 +295,7 @@ def test_basic_operations(TestObj): _var_name="bar", _var_type=str, _var_is_string=True, _var_is_local=True ) ) - == "{(state.foo.bar === `bar`)}" + == "{((state.foo.bar) === (`bar`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=TestObj)._var_set_state("state").bar) @@ -303,7 +303,7 @@ def test_basic_operations(TestObj): ) assert str(abs(v(1))) == "{Math.abs(1)}" assert str(v([1, 2, 3]).length()) == "{[1, 2, 3].length}" - assert str(v([1, 2]) + v([3, 4])) == "{spreadArraysOrObjects([1, 2] , [3, 4])}" + assert str(v([1, 2]) + v([3, 4])) == "{spreadArraysOrObjects(([1, 2]) , ([3, 4]))}" # Tests for reverse operation assert str(v([1, 2, 3]).reverse()) == "{[...[1, 2, 3]].reverse()}" @@ -319,55 +319,55 @@ def test_basic_operations(TestObj): assert str(BaseVar(_var_name="foo", _var_type=str)._type()) == "{typeof foo}" # type: ignore assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() == str) # type: ignore - == "{(typeof foo === `string`)}" + == "{((typeof foo) === (`string`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() == str) # type: ignore - == "{(typeof foo === `string`)}" + == "{((typeof foo) === (`string`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() == int) # type: ignore - == "{(typeof foo === `number`)}" + == "{((typeof foo) === (`number`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() == list) # type: ignore - == "{(typeof foo === `Array`)}" + == "{((typeof foo) === (`Array`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() == float) # type: ignore - == "{(typeof foo === `number`)}" + == "{((typeof foo) === (`number`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() == tuple) # type: ignore - == "{(typeof foo === `Array`)}" + == "{((typeof foo) === (`Array`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() == dict) # type: ignore - == "{(typeof foo === `Object`)}" + == "{((typeof foo) === (`Object`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() != str) # type: ignore - == "{(typeof foo !== `string`)}" + == "{((typeof foo) !== (`string`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() != int) # type: ignore - == "{(typeof foo !== `number`)}" + == "{((typeof foo) !== (`number`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() != list) # type: ignore - == "{(typeof foo !== `Array`)}" + == "{((typeof foo) !== (`Array`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() != float) # type: ignore - == "{(typeof foo !== `number`)}" + == "{((typeof foo) !== (`number`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() != tuple) # type: ignore - == "{(typeof foo !== `Array`)}" + == "{((typeof foo) !== (`Array`))}" ) assert ( str(BaseVar(_var_name="foo", _var_type=str)._type() != dict) # type: ignore - == "{(typeof foo !== `Object`)}" + == "{((typeof foo) !== (`Object`))}" )