@@ -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