[llvm] [Dexter] Add basic structured script parsing (PR #193710)
Orlando Cazalet-Hyams via llvm-commits
llvm-commits at lists.llvm.org
Thu May 7 08:24:50 PDT 2026
================
@@ -0,0 +1,202 @@
+# DExTer : Debugging Experience Tester
+# ~~~~~~ ~ ~~ ~ ~~
+#
+# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""This file defines all of the Nodes used to create Dexter scripts. All Nodes must be registered with the yaml
+constructor/representer in `setup_yaml_parser` before loading or printing any script.
+"""
+
+import abc
+from dataclasses import dataclass
+from typing import Any, Dict, Optional, Union
+import yaml
+from dex.dextIR.ValueIR import ValueIR
+from dex.utils.Exceptions import Error
+
+
+def setup_yaml_parser(loader):
+ reg_classes = [
+ Where,
+ Value,
+ DexRange,
+ ]
+ for c in reg_classes:
+ c.register_yaml(loader)
+
+
+class DexterNodeError(Error):
+ """Class representing errors with Dexter node parsing."""
+
+ def __init__(self, node, msg):
+ super(DexterNodeError, self).__init__(msg)
+ self.msg = msg
+ self.node = node
+
+ def __str__(self):
+ return f"Error with node: {self.node}: {self.msg}"
+
+
+###################
+## Structural Nodes: These are used as keys in the Script, and collectively define Dexter's actions when running a test:
+## how it steps and navigates through the debuggee program, and what information it collects from the
+## debugger.
+
+
+class Where:
+ """ "One or more instances of this class define a range of steps in a debugging session. Any expects in the script
+ within scope of a "Where" will only be evaluated for the steps where the Where applies.
+ """
+
+ def __init__(self, attributes: dict):
+ self.file: Optional[str] = attributes.pop("file", None)
+ self.function: Union[list[str], str, None] = attributes.pop("function", None)
+ self.lines: Union[int, DexRange, None] = attributes.pop("lines", None)
+ self.after_hit_count: Optional[int] = attributes.pop("after_hit_count", None)
+ self.for_hit_count: Optional[int] = attributes.pop("for_hit_count", None)
+ self.conditions: dict = attributes.pop("conditions", None)
+ if attributes:
+ raise DexterNodeError(
+ self, f"unexpected attributes {', '.join(attributes)}"
+ )
+ if (
+ not self.function
+ and not self.lines
+ and (self.for_hit_count or self.after_hit_count)
+ ):
+ raise DexterNodeError(
+ self, "can't check hit counts without an explicit lines or function arg"
+ )
+
+ def __repr__(self):
+ elts = [
+ f"{name}={value}"
+ for name, value in self.get_attrs().items()
+ if value is not None
+ ]
+ return f"Where(" + ", ".join(elts) + ")"
+
+ def get_attrs(self) -> Dict[str, Any]:
+ return {
+ "file": self.file,
+ "function": self.function,
+ "lines": self.lines,
+ "for_hit_count": self.for_hit_count,
+ "after_hit_count": self.after_hit_count,
+ "conditions": self.conditions,
+ }
+
+ @staticmethod
+ def constructor(loader: yaml.Loader, node):
+ return Where(loader.construct_mapping(node))
+
+ @staticmethod
+ def representer(dumper: yaml.Dumper, data: "Where"):
+ mapping = {
+ name: value for name, value in data.get_attrs().items() if value is not None
+ }
+ return dumper.represent_mapping("!where", mapping, flow_style=True)
+
+ @staticmethod
+ def register_yaml(loader):
+ yaml.add_constructor("!where", Where.constructor, loader)
+ yaml.add_representer(Where, Where.representer)
+
+ def get_lines(self) -> range:
+ """Returns the range of line numbers that this Where references, returning an empty range if this Where does not
+ refer to any lines."""
+ if not self.lines:
+ return range(-1)
----------------
OCHyams wrote:
`range(-1)` is this an empty range? (genuine question)
https://github.com/llvm/llvm-project/pull/193710
More information about the llvm-commits
mailing list