[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