Skip to content

Commit 4c6b9af

Browse files
committed
refactor(debugger): break down complex evaluate() method
1 parent 2edfeea commit 4c6b9af

File tree

1 file changed

+205
-138
lines changed

1 file changed

+205
-138
lines changed

packages/debugger/src/robotcode/debugger/debugger.py

Lines changed: 205 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -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 ***\nDummyTestCase423141592653589793\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 ***\nDummyTestCase423141592653589793\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

Comments
 (0)