@@ -1771,166 +1771,233 @@ def evaluate(
17711771        context : Union [EvaluateArgumentContext , str , None ] =  None ,
17721772        format : Optional [ValueFormat ] =  None ,
17731773    ) ->  EvaluateResult :
1774+         """Evaluate an expression in the context of a stack frame.""" 
17741775        if  not  expression :
17751776            return  EvaluateResult (result = "" )
17761777
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  (
17781814            (context  ==  EvaluateArgumentContext .REPL )
17791815            and  expression .startswith ("#" )
17801816            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+         )
17841818
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.""" 
17851821        stack_frame  =  next ((v  for  v  in  self .full_stack_frames  if  v .id  ==  frame_id ), None )
1786- 
17871822        evaluate_context  =  stack_frame .context () if  stack_frame  else  None 
17881823
17891824        if  evaluate_context  is  None :
17901825            evaluate_context  =  EXECUTION_CONTEXTS .current 
17911826
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+         )
17941847
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 ),
18071888            )
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 )
18321890
1833-                              result  =  self .run_in_robot_thread (run_kw )
1891+         result  =  self .run_in_robot_thread (run_kw )
18341892
1835-                              if  isinstance (result , BaseException ):
1836-                                  raise  result 
1893+         if  isinstance (result , BaseException ):
1894+             raise  result 
18371895
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 )
18691946                else :
1947+                     result  =  self ._execute_keywords_with_delayed_logging_legacy (test .body , evaluate_context )
1948+             return  result 
18701949
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 )
19261951
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 
19321954
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+                 )
19342001
19352002    def  _create_evaluate_result (self , value : Any ) ->  EvaluateResult :
19362003        if  isinstance (value , Mapping ):
0 commit comments