@@ -1771,166 +1771,233 @@ def evaluate(
1771
1771
context : Union [EvaluateArgumentContext , str , None ] = None ,
1772
1772
format : Optional [ValueFormat ] = None ,
1773
1773
) -> EvaluateResult :
1774
+ """Evaluate an expression in the context of a stack frame."""
1774
1775
if not expression :
1775
1776
return EvaluateResult (result = "" )
1776
1777
1777
- if (
1778
+ # Handle expression mode toggle
1779
+ if self ._is_expression_mode_toggle (expression , context ):
1780
+ self .expression_mode = not self .expression_mode
1781
+ return EvaluateResult (result = "# Expression mode is now " + ("on" if self .expression_mode else "off" ))
1782
+
1783
+ # Get evaluation context
1784
+ stack_frame , evaluate_context = self ._get_evaluation_context (frame_id )
1785
+ if evaluate_context is None :
1786
+ return EvaluateResult (result = "Unable to evaluate expression. No context available." , type = "FatalError" )
1787
+
1788
+ # Process CURDIR substitution
1789
+ processed_expression = self ._process_curdir_substitution (expression , stack_frame )
1790
+ if isinstance (processed_expression , EvaluateResult ):
1791
+ return processed_expression
1792
+
1793
+ # Get variables context
1794
+ vars = self ._get_variables_context (stack_frame , evaluate_context )
1795
+
1796
+ # Evaluate expression
1797
+ try :
1798
+ if self ._is_expression_mode (context ):
1799
+ result = self ._evaluate_expression_mode (processed_expression , vars , evaluate_context , context )
1800
+ else :
1801
+ result = self ._evaluate_repl_mode (processed_expression , vars , evaluate_context )
1802
+
1803
+ except (SystemExit , KeyboardInterrupt ):
1804
+ raise
1805
+ except BaseException as e :
1806
+ self ._logger .exception (e )
1807
+ raise
1808
+
1809
+ return self ._create_evaluate_result (result )
1810
+
1811
+ def _is_expression_mode_toggle (self , expression : str , context : Union [EvaluateArgumentContext , str , None ]) -> bool :
1812
+ """Check if expression is a command to toggle expression mode."""
1813
+ return (
1778
1814
(context == EvaluateArgumentContext .REPL )
1779
1815
and expression .startswith ("#" )
1780
1816
and expression [1 :].strip () == "exprmode"
1781
- ):
1782
- self .expression_mode = not self .expression_mode
1783
- return EvaluateResult (result = "# Expression mode is now " + ("on" if self .expression_mode else "off" ))
1817
+ )
1784
1818
1819
+ def _get_evaluation_context (self , frame_id : Optional [int ]) -> tuple [Optional [StackFrameEntry ], Any ]:
1820
+ """Get the stack frame and evaluation context for the given frame ID."""
1785
1821
stack_frame = next ((v for v in self .full_stack_frames if v .id == frame_id ), None )
1786
-
1787
1822
evaluate_context = stack_frame .context () if stack_frame else None
1788
1823
1789
1824
if evaluate_context is None :
1790
1825
evaluate_context = EXECUTION_CONTEXTS .current
1791
1826
1792
- if stack_frame is None and evaluate_context is None :
1793
- return EvaluateResult (result = "Unable to evaluate expression. No context available." , type = "FatalError" )
1827
+ return stack_frame , evaluate_context
1828
+
1829
+ def _process_curdir_substitution (
1830
+ self , expression : str , stack_frame : Optional [StackFrameEntry ]
1831
+ ) -> Union [str , EvaluateResult ]:
1832
+ """Process ${CURDIR} substitution in expression."""
1833
+ if stack_frame is not None and stack_frame .source is not None :
1834
+ curdir = str (Path (stack_frame .source ).parent )
1835
+ expression = self .CURRDIR .sub (curdir .replace ("\\ " , "\\ \\ " ), expression )
1836
+ if expression == curdir :
1837
+ return EvaluateResult (repr (expression ), repr (type (expression )))
1838
+ return expression
1839
+
1840
+ def _get_variables_context (self , stack_frame : Optional [StackFrameEntry ], evaluate_context : Any ) -> Any :
1841
+ """Get the variables context for evaluation."""
1842
+ return (
1843
+ (stack_frame .get_first_or_self ().variables () or evaluate_context .variables .current )
1844
+ if stack_frame is not None
1845
+ else evaluate_context .variables ._global
1846
+ )
1794
1847
1795
- result : Any = None
1796
- try :
1797
- if stack_frame is not None and stack_frame .source is not None :
1798
- curdir = str (Path (stack_frame .source ).parent )
1799
- expression = self .CURRDIR .sub (curdir .replace ("\\ " , "\\ \\ " ), expression )
1800
- if expression == curdir :
1801
- return EvaluateResult (repr (expression ), repr (type (expression )))
1802
-
1803
- vars = (
1804
- (stack_frame .get_first_or_self ().variables () or evaluate_context .variables .current )
1805
- if stack_frame is not None
1806
- else evaluate_context .variables ._global
1848
+ def _is_expression_mode (self , context : Union [EvaluateArgumentContext , str , None ]) -> bool :
1849
+ """Check if we should use expression mode for evaluation."""
1850
+ return (
1851
+ isinstance (context , EvaluateArgumentContext ) and context != EvaluateArgumentContext .REPL
1852
+ ) or self .expression_mode
1853
+
1854
+ def _evaluate_expression_mode (
1855
+ self , expression : str , vars : Any , evaluate_context : Any , context : Union [EvaluateArgumentContext , str , None ]
1856
+ ) -> Any :
1857
+ """Evaluate expression in expression mode."""
1858
+ if expression .startswith ("! " ):
1859
+ return self ._evaluate_keyword_expression (expression , evaluate_context )
1860
+ if self .IS_VARIABLE_RE .match (expression .strip ()):
1861
+ return self ._evaluate_variable_expression (expression , vars , context )
1862
+ return internal_evaluate_expression (vars .replace_string (expression ), vars )
1863
+
1864
+ def _evaluate_keyword_expression (self , expression : str , evaluate_context : Any ) -> Any :
1865
+ """Evaluate a keyword expression (starting with '! ')."""
1866
+ splitted = self .SPLIT_LINE .split (expression [2 :].strip ())
1867
+
1868
+ if not splitted :
1869
+ return None
1870
+
1871
+ # Extract variable assignments
1872
+ variables : List [str ] = []
1873
+ while len (splitted ) > 1 and self .IS_VARIABLE_ASSIGNMENT_RE .match (splitted [0 ].strip ()):
1874
+ var = splitted [0 ]
1875
+ splitted = splitted [1 :]
1876
+ if var .endswith ("=" ):
1877
+ var = var [:- 1 ]
1878
+ variables .append (var )
1879
+
1880
+ if not splitted :
1881
+ return None
1882
+
1883
+ def run_kw () -> Any :
1884
+ kw = Keyword (
1885
+ name = splitted [0 ],
1886
+ args = tuple (splitted [1 :]),
1887
+ assign = tuple (variables ),
1807
1888
)
1808
- if (
1809
- isinstance (context , EvaluateArgumentContext ) and context != EvaluateArgumentContext .REPL
1810
- ) or self .expression_mode :
1811
- if expression .startswith ("! " ):
1812
- splitted = self .SPLIT_LINE .split (expression [2 :].strip ())
1813
-
1814
- if splitted :
1815
- variables : List [str ] = []
1816
- while len (splitted ) > 1 and self .IS_VARIABLE_ASSIGNMENT_RE .match (splitted [0 ].strip ()):
1817
- var = splitted [0 ]
1818
- splitted = splitted [1 :]
1819
- if var .endswith ("=" ):
1820
- var = var [:- 1 ]
1821
- variables .append (var )
1822
-
1823
- if splitted :
1824
-
1825
- def run_kw () -> Any :
1826
- kw = Keyword (
1827
- name = splitted [0 ],
1828
- args = tuple (splitted [1 :]),
1829
- assign = tuple (variables ),
1830
- )
1831
- return self ._run_keyword (kw , evaluate_context )
1889
+ return self ._run_keyword (kw , evaluate_context )
1832
1890
1833
- result = self .run_in_robot_thread (run_kw )
1891
+ result = self .run_in_robot_thread (run_kw )
1834
1892
1835
- if isinstance (result , BaseException ):
1836
- raise result
1893
+ if isinstance (result , BaseException ):
1894
+ raise result
1837
1895
1838
- elif self .IS_VARIABLE_RE .match (expression .strip ()):
1839
- try :
1840
- result = vars .replace_scalar (expression )
1841
- except VariableError :
1842
- if context is not None and (
1843
- (
1844
- isinstance (context , EvaluateArgumentContext )
1845
- and (
1846
- context
1847
- in [
1848
- EvaluateArgumentContext .HOVER ,
1849
- EvaluateArgumentContext .WATCH ,
1850
- ]
1851
- )
1852
- )
1853
- or context
1854
- in [
1855
- EvaluateArgumentContext .HOVER .value ,
1856
- EvaluateArgumentContext .WATCH .value ,
1857
- ]
1858
- ):
1859
- result = UNDEFINED
1860
- else :
1861
- raise
1862
- else :
1863
- result = internal_evaluate_expression (vars .replace_string (expression ), vars )
1864
- else :
1865
- parts = self .SPLIT_LINE .split (expression .strip ())
1866
- if parts and len (parts ) == 1 and self .IS_VARIABLE_RE .match (parts [0 ].strip ()):
1867
- # result = vars[parts[0].strip()]
1868
- result = vars .replace_scalar (parts [0 ].strip ())
1896
+ return result
1897
+
1898
+ def _evaluate_variable_expression (
1899
+ self , expression : str , vars : Any , context : Union [EvaluateArgumentContext , str , None ]
1900
+ ) -> Any :
1901
+ """Evaluate a variable expression."""
1902
+ try :
1903
+ return vars .replace_scalar (expression )
1904
+ except VariableError :
1905
+ if self ._should_return_undefined_for_variable_error (context ):
1906
+ return UNDEFINED
1907
+ raise
1908
+
1909
+ def _should_return_undefined_for_variable_error (self , context : Union [EvaluateArgumentContext , str , None ]) -> bool :
1910
+ """Check if we should return UNDEFINED for variable errors in certain contexts."""
1911
+ return context is not None and (
1912
+ (
1913
+ isinstance (context , EvaluateArgumentContext )
1914
+ and context in [EvaluateArgumentContext .HOVER , EvaluateArgumentContext .WATCH ]
1915
+ )
1916
+ or context in [EvaluateArgumentContext .HOVER .value , EvaluateArgumentContext .WATCH .value ]
1917
+ )
1918
+
1919
+ def _evaluate_repl_mode (self , expression : str , vars : Any , evaluate_context : Any ) -> Any :
1920
+ """Evaluate expression in REPL mode."""
1921
+ parts = self .SPLIT_LINE .split (expression .strip ())
1922
+ if parts and len (parts ) == 1 and self .IS_VARIABLE_RE .match (parts [0 ].strip ()):
1923
+ return vars .replace_scalar (parts [0 ].strip ())
1924
+ return self ._evaluate_test_body_expression (expression , evaluate_context )
1925
+
1926
+ def _evaluate_test_body_expression (self , expression : str , evaluate_context : Any ) -> Any :
1927
+ """Evaluate a test body expression (Robot Framework commands)."""
1928
+
1929
+ def get_test_body_from_string (command : str ) -> TestCase :
1930
+ suite_str = (
1931
+ "*** Test Cases ***\n DummyTestCase423141592653589793\n "
1932
+ + ("\n " .join (command .split ("\n " )) if "\n " in command else command )
1933
+ ) + "\n "
1934
+
1935
+ model = get_model (suite_str )
1936
+ suite : TestSuite = TestSuite .from_model (model )
1937
+ return cast (TestCase , suite .tests [0 ])
1938
+
1939
+ def run_kw () -> Any :
1940
+ test = get_test_body_from_string (expression )
1941
+ result = None
1942
+
1943
+ if len (test .body ):
1944
+ if get_robot_version () >= (7 , 3 ):
1945
+ result = self ._execute_keywords_with_delayed_logging_v73 (test .body , evaluate_context )
1869
1946
else :
1947
+ result = self ._execute_keywords_with_delayed_logging_legacy (test .body , evaluate_context )
1948
+ return result
1870
1949
1871
- def get_test_body_from_string (command : str ) -> TestCase :
1872
- suite_str = (
1873
- "*** Test Cases ***\n DummyTestCase423141592653589793\n "
1874
- + ("\n " .join (command .split ("\n " )) if "\n " in command else command )
1875
- ) + "\n "
1876
-
1877
- model = get_model (suite_str )
1878
- suite : TestSuite = TestSuite .from_model (model )
1879
- return cast (TestCase , suite .tests [0 ])
1880
-
1881
- def run_kw () -> Any :
1882
- test = get_test_body_from_string (expression )
1883
- result = None
1884
-
1885
- if len (test .body ):
1886
- if get_robot_version () >= (7 , 3 ):
1887
- for kw in test .body :
1888
- with evaluate_context .output .delayed_logging :
1889
- try :
1890
- result = self ._run_keyword (kw , evaluate_context )
1891
- except (SystemExit , KeyboardInterrupt ):
1892
- raise
1893
- except BaseException as e :
1894
- result = e
1895
- break
1896
- else :
1897
- for kw in test .body :
1898
- with LOGGER .delayed_logging :
1899
- try :
1900
- result = self ._run_keyword (kw , evaluate_context )
1901
- except (SystemExit , KeyboardInterrupt ):
1902
- raise
1903
- except BaseException as e :
1904
- result = e
1905
- break
1906
- finally :
1907
- if get_robot_version () <= (7 , 2 ):
1908
- messages = LOGGER ._log_message_cache or []
1909
- for msg in messages or ():
1910
- # hack to get and evaluate log level
1911
- listener : Any = next (iter (LOGGER ), None )
1912
- if listener is None or self .check_message_is_logged (listener , msg ):
1913
- self .log_message (
1914
- {
1915
- "level" : msg .level ,
1916
- "message" : msg .message ,
1917
- "timestamp" : msg .timestamp ,
1918
- }
1919
- )
1920
- return result
1921
-
1922
- result = self .run_in_robot_thread (run_kw )
1923
-
1924
- if isinstance (result , BaseException ):
1925
- raise result
1950
+ result = self .run_in_robot_thread (run_kw )
1926
1951
1927
- except (SystemExit , KeyboardInterrupt ):
1928
- raise
1929
- except BaseException as e :
1930
- self ._logger .exception (e )
1931
- raise
1952
+ if isinstance (result , BaseException ):
1953
+ raise result
1932
1954
1933
- return self ._create_evaluate_result (result )
1955
+ return result
1956
+
1957
+ def _execute_keywords_with_delayed_logging_v73 (self , keywords : Any , evaluate_context : Any ) -> Any :
1958
+ """Execute keywords with delayed logging for Robot Framework >= 7.3."""
1959
+ result = None
1960
+ for kw in keywords :
1961
+ with evaluate_context .output .delayed_logging :
1962
+ try :
1963
+ result = self ._run_keyword (kw , evaluate_context )
1964
+ except (SystemExit , KeyboardInterrupt ):
1965
+ raise
1966
+ except BaseException as e :
1967
+ result = e
1968
+ break
1969
+ return result
1970
+
1971
+ def _execute_keywords_with_delayed_logging_legacy (self , keywords : Any , evaluate_context : Any ) -> Any :
1972
+ """Execute keywords with delayed logging for Robot Framework < 7.3."""
1973
+ result = None
1974
+ for kw in keywords :
1975
+ with LOGGER .delayed_logging :
1976
+ try :
1977
+ result = self ._run_keyword (kw , evaluate_context )
1978
+ except (SystemExit , KeyboardInterrupt ):
1979
+ raise
1980
+ except BaseException as e :
1981
+ result = e
1982
+ break
1983
+ finally :
1984
+ if get_robot_version () <= (7 , 2 ):
1985
+ self ._process_delayed_log_messages ()
1986
+ return result
1987
+
1988
+ def _process_delayed_log_messages (self ) -> None :
1989
+ """Process delayed log messages for older Robot Framework versions."""
1990
+ messages = LOGGER ._log_message_cache or []
1991
+ for msg in messages or ():
1992
+ listener : Any = next (iter (LOGGER ), None )
1993
+ if listener is None or self .check_message_is_logged (listener , msg ):
1994
+ self .log_message (
1995
+ {
1996
+ "level" : msg .level ,
1997
+ "message" : msg .message ,
1998
+ "timestamp" : msg .timestamp ,
1999
+ }
2000
+ )
1934
2001
1935
2002
def _create_evaluate_result (self , value : Any ) -> EvaluateResult :
1936
2003
if isinstance (value , Mapping ):
0 commit comments