Class XmlNodeExtensions
Extension methods for XmlNodes.
Inheritance
- Object
- XmlNodeExtensions
Declaration
public static class XmlNodeExtensions : Object
Methods
ChildrenInContext(XmlNode)
Returns an array containing each immediate child of
value
paired with a function to replace the child.
This is typically useful when you need to replace a node's children one at a time,
such as during mutation testing.
The replacement function can be seen as the "context" of the child; calling the function with a new child "plugs the hole" in the context.
SelfAndDescendantsInContext<T>(IRewriter<T>, T)DescendantsAndSelfInContext<T>(IRewriter<T>, T)Declaration
public static ValueTuple<XmlNode, Func<XmlNode, XmlNode>>[] ChildrenInContext(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to get the contexts for the immediate children |
Returns
Type | Description |
---|---|
ValueTuple<XmlNode, Func<XmlNode, XmlNode>>[] |
See Also
CountChildren(XmlNode)
Count the immediate children of the value. CountChildren()
Declaration
public static int CountChildren(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value |
Returns
Type | Description |
---|---|
|
Examples
Given a representation of the expression (1+2)+3
,
Expr expr = new Add(
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
);
CountChildren(T) counts the immediate children of the topmost (Add) node.
Assert.Equal(2, rewriter.CountChildren(expr));
See Also
Cursor(XmlNode)
Create a Cursor<T>(IRewriter<T>, T) focused on the root node of value
.
Declaration
public static Cursor<XmlNode> Cursor(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The root node on which the newly created Cursor<T>(IRewriter<T>, T) should be focused |
Returns
Type | Description |
---|---|
A Cursor<T>(IRewriter<T>, T) focused on the root node of |
See Also
DescendantAt(XmlNode, IEnumerable<Direction>)
Returns the descendant at a particular location in value
Declaration
public static XmlNode DescendantAt(this XmlNode value, IEnumerable<Direction> path)
Parameters
Type | Name | Description |
---|---|---|
value |
The rewritable tree type |
|
path |
The route to take to find the descendant |
Returns
Type | Description |
---|---|
The descendant found by following the directions in |
Exceptions
Type | Condition |
---|---|
Thrown if |
See Also
DescendantsAndSelf(XmlNode)
Yields all of the nodes in the tree represented by value
, starting at the bottom.
This is a depth-first post-order traversal.
SelfAndDescendants<T>(IRewriter<T>, T)Declaration
public static IEnumerable<XmlNode> DescendantsAndSelf(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to traverse |
Returns
Type | Description |
---|---|
An enumerable containing all of the nodes in the tree represented by |
Examples
Expr expr = new Add(
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
);
Expr[] expected = new[]
{
new Lit(1),
new Lit(2),
new Add(new Lit(1), new Lit(2)),
new Lit(3),
expr
};
Assert.Equal(expected, rewriter.DescendantsAndSelf(expr));
See Also
DescendantsAndSelfInContext(XmlNode)
Yields each node in the tree represented by value
paired with a function to replace the node, starting at the bottom.
This is typically useful when you need to replace nodes one at a time,
such as during mutation testing.
The replacement function can be seen as the "context" of the node; calling the function with a new node "plugs the hole" in the context.
This is a depth-first post-order traversal.
DescendantsAndSelf<T>(IRewriter<T>, T)ChildrenInContext<T>(IRewriter<T>, T)SelfAndDescendantsInContext<T>(IRewriter<T>, T)Declaration
public static IEnumerable<ValueTuple<XmlNode, Func<XmlNode, XmlNode>>> DescendantsAndSelfInContext(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to get the contexts for the descendants |
Returns
Type | Description |
---|---|
See Also
Fold<T>(XmlNode, SpanFunc<T, XmlNode, T>)
Flattens all the nodes in the tree represented by value
into a single result,
using an aggregation function to combine each node with the results of folding its children.
Declaration
public static T Fold<T>(this XmlNode value, SpanFunc<T, XmlNode, T> func)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to fold |
|
func |
The aggregation function |
Returns
Type | Description |
---|---|
T |
The result of aggregating the tree represented by |
Type Parameters
Name | Description |
---|---|
T |
See Also
Fold<T>(XmlNode, Func<Memory<T>, XmlNode, ValueTask<T>>)
Flattens all the nodes in the tree represented by value
into a single result,
using an asynchronous aggregation function to combine each node with the results of folding its children.
Declaration
public static ValueTask<T> Fold<T>(this XmlNode value, Func<Memory<T>, XmlNode, ValueTask<T>> func)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to fold |
|
func |
The asynchronous aggregation function |
Returns
Type | Description |
---|---|
ValueTask<T> |
The result of aggregating the tree represented by |
Type Parameters
Name | Description |
---|---|
T |
Remarks
This method is not available on platforms which do not support ValueTask.
See Also
GetChildren(XmlNode)
Get the immediate children of the value. GetChildren(Span<T>)
Declaration
public static XmlNode[] GetChildren(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value |
Returns
Type | Description |
---|---|
XmlNode[] |
The immediate children of |
Examples
Given a representation of the expression (1+2)+3
,
Expr expr = new Add(
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
);
GetChildren<T>(IRewriter<T>, T) returns the immediate children of the topmost node.
Expr[] expected = new[]
{
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
};
Assert.Equal(expected, rewriter.GetChildren(expr));
See Also
GetChildren(XmlNode, Span<XmlNode>)
Copy the immediate children of the value into childrenReceiver
.
GetChildren(Span<T>)
Declaration
public static void GetChildren(this XmlNode value, Span<XmlNode> childrenReceiver)
Parameters
Type | Name | Description |
---|---|---|
value |
The value |
|
childrenReceiver |
A Span<T> to copy |
Examples
Given a representation of the expression (1+2)+3
,
Expr expr = new Add(
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
);
GetChildren(Span<T>, T) copies the immediate children of the topmost node into the span.
Expr[] expected = new[]
{
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
};
var array = new Expr[rewriter.CountChildren(expr)];
rewriter.GetChildren(array, expr);
Assert.Equal(expected, array);
See Also
ReplaceDescendantAt<T>(XmlNode, IEnumerable<Direction>, XmlNode)
Replaces the descendant at a particular location in value
Declaration
public static XmlNode ReplaceDescendantAt<T>(this XmlNode value, IEnumerable<Direction> path, XmlNode newDescendant)
Parameters
Type | Name | Description |
---|---|---|
value |
The rewritable tree type |
|
path |
The route to take to find the descendant |
|
newDescendant |
The replacement descendant |
Returns
Type | Description |
---|---|
A copy of |
Type Parameters
Name | Description |
---|---|
T |
Exceptions
Type | Condition |
---|---|
Thrown if |
See Also
Rewrite(XmlNode, Func<XmlNode, ValueTask<XmlNode>>)
Rebuild a tree by applying an asynchronous transformation function to every node from bottom to top.
Declaration
public static ValueTask<XmlNode> Rewrite(this XmlNode value, Func<XmlNode, ValueTask<XmlNode>> transformer)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to rewrite |
|
transformer |
The asynchronous transformation function to apply to every node in the tree |
Returns
Type | Description |
---|---|
The result of applying |
Remarks
This method is not available on platforms which do not support ValueTask.
Examples
Given a representation of the expression (1+2)+3
,
Expr expr = new Add(
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
);
Rewrite<T>(IRewriter<T>, Func<T, ValueTask<T>>, T) replaces the leaves of the tree with the result of calling transformer
,
then replaces their parents with the result of calling transformer
, and so on.
By the end, Rewrite<T>(IRewriter<T>, Func<T, ValueTask<T>>, T) has traversed the whole tree.
Expr expected = await transformer(new Add(
await transformer(new Add(
await transformer(new Lit(1)),
await transformer(new Lit(2))
)),
await transformer(new Lit(3))
));
Assert.Equal(expected, await rewriter.Rewrite(transformer, expr));
See Also
Rewrite(XmlNode, Func<XmlNode, XmlNode>)
Rebuild a tree by applying a transformation function to every node from bottom to top.
Declaration
public static XmlNode Rewrite(this XmlNode value, Func<XmlNode, XmlNode> transformer)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to rewrite |
|
transformer |
The transformation function to apply to every node in the tree |
Returns
Type | Description |
---|---|
The result of applying |
Examples
Given a representation of the expression (1+2)+3
,
Expr expr = new Add(
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
);
Rewrite<T>(IRewriter<T>, Func<T, T>, T) replaces the leaves of the tree with the result of calling transformer
,
then replaces their parents with the result of calling transformer
, and so on.
By the end, Rewrite<T>(IRewriter<T>, Func<T, T>, T) has traversed the whole tree.
Expr expected = transformer(new Add(
transformer(new Add(
transformer(new Lit(1)),
transformer(new Lit(2))
)),
transformer(new Lit(3))
));
Assert.Equal(expected, rewriter.Rewrite(transformer, expr));
See Also
RewriteChildren(XmlNode, Func<XmlNode, ValueTask<XmlNode>>)
Update the immediate children of the value by applying an asynchronous transformation function to each one.
Declaration
public static ValueTask<XmlNode> RewriteChildren(this XmlNode value, Func<XmlNode, ValueTask<XmlNode>> transformer)
Parameters
Type | Name | Description |
---|---|---|
value |
The old value, whose immediate children should be transformed by |
|
transformer |
An asynchronous transformation function to apply to each of |
Returns
Type | Description |
---|---|
A copy of |
Remarks
This method is not available on platforms which do not support ValueTask.
See Also
RewriteChildren(XmlNode, Func<XmlNode, XmlNode>)
Update the immediate children of the value by applying a transformation function to each one.
Declaration
public static XmlNode RewriteChildren(this XmlNode value, Func<XmlNode, XmlNode> transformer)
Parameters
Type | Name | Description |
---|---|---|
value |
The old value, whose immediate children should be transformed by |
|
transformer |
A transformation function to apply to each of |
Returns
Type | Description |
---|---|
A copy of |
See Also
RewriteDescendantAt<T>(XmlNode, IEnumerable<Direction>, Func<XmlNode, ValueTask<XmlNode>>)
Apply an asynchronous function at a particular location in value
Declaration
public static ValueTask<XmlNode> RewriteDescendantAt<T>(this XmlNode value, IEnumerable<Direction> path, Func<XmlNode, ValueTask<XmlNode>> transformer)
Parameters
Type | Name | Description |
---|---|---|
value |
The rewritable tree type |
|
path |
The route to take to find the descendant |
|
transformer |
An asynchronous function to calculate a replacement for the descendant |
Returns
Type | Description |
---|---|
A copy of |
Type Parameters
Name | Description |
---|---|
T |
Remarks
This method is not available on platforms which do not support ValueTask.
Exceptions
Type | Condition |
---|---|
Thrown if |
See Also
RewriteDescendantAt<T>(XmlNode, IEnumerable<Direction>, Func<XmlNode, XmlNode>)
Apply a function at a particular location in value
Declaration
public static XmlNode RewriteDescendantAt<T>(this XmlNode value, IEnumerable<Direction> path, Func<XmlNode, XmlNode> transformer)
Parameters
Type | Name | Description |
---|---|---|
value |
The rewritable tree type |
|
path |
The route to take to find the descendant |
|
transformer |
A function to calculate a replacement for the descendant |
Returns
Type | Description |
---|---|
A copy of |
Type Parameters
Name | Description |
---|---|
T |
Exceptions
Type | Condition |
---|---|
Thrown if |
See Also
RewriteIter(XmlNode, Func<XmlNode, ValueTask<XmlNode>>)
Rebuild a tree by repeatedly applying an asynchronous transformation function to every node in the tree,
until a fixed point is reached. transformer
should always eventually return
its argument unchanged, or this method will loop.
That is, x.RewriteIter(transformer).SelfAndDescendants().All(x => await transformer(x) == x)
.
This is typically useful when you want to put your tree into a normal form by applying a collection of rewrite rules until none of them can fire any more.
Declaration
public static ValueTask<XmlNode> RewriteIter(this XmlNode value, Func<XmlNode, ValueTask<XmlNode>> transformer)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to rewrite |
|
transformer |
An asynchronous transformation function to apply to every node in |
Returns
Type | Description |
---|---|
The result of applying |
Remarks
This method is not available on platforms which do not support ValueTask.
See Also
RewriteIter(XmlNode, Func<XmlNode, XmlNode>)
Rebuild a tree by repeatedly applying a transformation function to every node in the tree,
until a fixed point is reached. transformer
should always eventually return
its argument unchanged, or this method will loop.
That is, x.RewriteIter(transformer).SelfAndDescendants().All(x => transformer(x) == x)
.
This is typically useful when you want to put your tree into a normal form by applying a collection of rewrite rules until none of them can fire any more.
Declaration
public static XmlNode RewriteIter(this XmlNode value, Func<XmlNode, XmlNode> transformer)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to rewrite |
|
transformer |
A transformation function to apply to every node in |
Returns
Type | Description |
---|---|
The result of applying |
See Also
SelfAndDescendants(XmlNode)
Yields all of the nodes in the tree represented by value
, starting at the top.
This is a depth-first pre-order traversal.
DescendantsAndSelf<T>(IRewriter<T>, T)Declaration
public static IEnumerable<XmlNode> SelfAndDescendants(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to traverse |
Returns
Type | Description |
---|---|
An enumerable containing all of the nodes in the tree represented by |
Examples
Expr expr = new Add(
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
);
Expr[] expected = new[]
{
expr,
new Add(new Lit(1), new Lit(2)),
new Lit(1),
new Lit(2),
new Lit(3),
};
Assert.Equal(expected, rewriter.SelfAndDescendants(expr));
See Also
SelfAndDescendantsBreadthFirst(XmlNode)
Yields all of the nodes in the tree represented by value
in a breadth-first traversal order.
This is a breadth-first pre-order traversal.
Declaration
public static IEnumerable<XmlNode> SelfAndDescendantsBreadthFirst(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to traverse |
Returns
Type | Description |
---|---|
An enumerable containing all of the nodes in the tree represented by |
See Also
SelfAndDescendantsInContext(XmlNode)
Yields each node in the tree represented by value
paired with a function to replace the node, starting at the top.
This is typically useful when you need to replace nodes one at a time,
such as during mutation testing.
The replacement function can be seen as the "context" of the node; calling the function with a new node "plugs the hole" in the context.
This is a depth-first pre-order traversal.
SelfAndDescendants<T>(IRewriter<T>, T)ChildrenInContext<T>(IRewriter<T>, T)DescendantsAndSelfInContext<T>(IRewriter<T>, T)Declaration
public static IEnumerable<ValueTuple<XmlNode, Func<XmlNode, XmlNode>>> SelfAndDescendantsInContext(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to get the contexts for the descendants |
Returns
Type | Description |
---|---|
See Also
SelfAndDescendantsInContextBreadthFirst(XmlNode)
Yields each node in the tree represented by value
paired with a function to replace the node, in a breadth-first traversal order.
This is typically useful when you need to replace nodes one at a time,
such as during mutation testing.
The replacement function can be seen as the "context" of the node; calling the function with a new node "plugs the hole" in the context.
This is a breadth-first pre-order traversal.
SelfAndDescendants<T>(IRewriter<T>, T)ChildrenInContext<T>(IRewriter<T>, T)DescendantsAndSelfInContext<T>(IRewriter<T>, T)Declaration
public static IEnumerable<ValueTuple<XmlNode, Func<XmlNode, XmlNode>>> SelfAndDescendantsInContextBreadthFirst(this XmlNode value)
Parameters
Type | Name | Description |
---|---|---|
value |
The value to get the contexts for the descendants |
Returns
Type | Description |
---|---|
See Also
SetChildren(XmlNode, ReadOnlySpan<XmlNode>)
Set the immediate children of the value.
Callers should ensure that newChildren
contains the same number of children as was returned by
GetChildren(Span<T>, T).
Declaration
public static XmlNode SetChildren(this XmlNode value, ReadOnlySpan<XmlNode> newChildren)
Parameters
Type | Name | Description |
---|---|---|
value |
The old value, whose immediate children should be replaced |
|
newChildren |
The new children |
Returns
Type | Description |
---|---|
A copy of |
Examples
Given a representation of the expression (1+2)+3
,
Expr expr = new Add(
new Add(
new Lit(1),
new Lit(2)
),
new Lit(3)
);
SetChildren(ReadOnlySpan<T>, T) replaces the immediate children of the topmost node.
Expr expected = new Add(
new Lit(4),
new Lit(5)
);
Assert.Equal(expected, rewriter.SetChildren(Children.Two(new Lit(4), new Lit(5)), expr));
See Also
ZipFold<U>(XmlNode, XmlNode, Func<XmlNode, XmlNode, IAsyncEnumerable<U>, ValueTask<U>>)
Flatten all of the nodes in the trees represented by values
into a single value at the same time, using an aggregation function to combine
nodes with the results of aggregating their children.
The trees are iterated in lock-step, much like an n-ary
Zip<TFirst,TSecond,TResult>(IEnumerable<TFirst>, IEnumerable<TSecond>, Func<TFirst,TSecond,TResult>).
When trees are not the same size, the larger ones are truncated both horizontally and vertically. That is, if a pair of nodes have a different number of children, the rightmost children of the larger of the two nodes are discarded.
Declaration
public static ValueTask<U> ZipFold<U>(this XmlNode value1, XmlNode value2, Func<XmlNode, XmlNode, IAsyncEnumerable<U>, ValueTask<U>> func)
Parameters
Type | Name | Description |
---|---|---|
value1 |
||
value2 |
||
Func<XmlNode, XmlNode, IAsyncEnumerable<U>, ValueTask<U>> |
func |
The aggregation function |
Returns
Type | Description |
---|---|
ValueTask<U> |
The result of aggregating the two trees |
Type Parameters
Name | Description |
---|---|
U |
Remarks
This method is not available on platforms which do not support ValueTask and IAsyncEnumerable<T>.
Examples
Here's an example of using ZipFold<T, U>(IRewriter<T>, Func<T[], IAsyncEnumerable<U>, ValueTask<U>>, T[]) to test if two trees are syntactically equal.
static bool Equals(this Expr left, Expr right)
=> left.ZipFold<Expr, bool>(
right,
(xs, results) =>
{
switch (xs[0])
{
case Add a1 when xs[1] is Add a2:
return results.All(x => x);
case Lit l1 when xs[1] is Lit l2:
return l1.Value == l2.Value;
default:
return false;
}
}
);
See Also
ZipFold<U>(XmlNode, XmlNode, Func<XmlNode, XmlNode, IEnumerable<U>, U>)
Flatten all of the nodes in the trees represented by values
into a single value at the same time, using an aggregation function to combine
nodes with the results of aggregating their children.
The trees are iterated in lock-step, much like an n-ary
Zip<TFirst,TSecond,TResult>(IEnumerable<TFirst>, IEnumerable<TSecond>, Func<TFirst,TSecond,TResult>).
When trees are not the same size, the larger ones are truncated both horizontally and vertically. That is, if a pair of nodes have a different number of children, the rightmost children of the larger of the two nodes are discarded.
Declaration
public static U ZipFold<U>(this XmlNode value1, XmlNode value2, Func<XmlNode, XmlNode, IEnumerable<U>, U> func)
Parameters
Type | Name | Description |
---|---|---|
value1 |
||
value2 |
||
Func<XmlNode, XmlNode, IEnumerable<U>, U> |
func |
The aggregation function |
Returns
Type | Description |
---|---|
U |
The result of aggregating the two trees |
Type Parameters
Name | Description |
---|---|
U |
Examples
Here's an example of using ZipFold<T, U>(IRewriter<T>, Func<T[], IEnumerable<U>, U>, T[]) to test if two trees are syntactically equal.
static bool Equals(this Expr left, Expr right)
=> left.ZipFold<Expr, bool>(
right,
(xs, results) =>
{
switch (xs[0])
{
case Add a1 when xs[1] is Add a2:
return results.All(x => x);
case Lit l1 when xs[1] is Lit l2:
return l1.Value == l2.Value;
default:
return false;
}
}
);
See Also
ZipFold<U>(XmlNode[], Func<XmlNode[], IAsyncEnumerable<U>, ValueTask<U>>)
Flatten all of the nodes in the trees represented by values
into a single value at the same time, using an aggregation function to combine
nodes with the results of aggregating their children.
The trees are iterated in lock-step, much like an n-ary
Zip<TFirst,TSecond,TResult>(IEnumerable<TFirst>, IEnumerable<TSecond>, Func<TFirst,TSecond,TResult>).
When trees are not the same size, the larger ones are truncated both horizontally and vertically. That is, if a pair of nodes have a different number of children, the rightmost children of the larger of the two nodes are discarded.
Declaration
public static ValueTask<U> ZipFold<U>(this XmlNode[] values, Func<XmlNode[], IAsyncEnumerable<U>, ValueTask<U>> func)
Parameters
Type | Name | Description |
---|---|---|
XmlNode[] |
values |
The trees to fold |
Func<XmlNode[], IAsyncEnumerable<U>, ValueTask<U>> |
func |
The aggregation function |
Returns
Type | Description |
---|---|
ValueTask<U> |
The result of aggregating the two trees |
Type Parameters
Name | Description |
---|---|
U |
Remarks
This method is not available on platforms which do not support ValueTask and IAsyncEnumerable<T>.
Examples
Here's an example of using ZipFold<T, U>(IRewriter<T>, Func<T[], IAsyncEnumerable<U>, ValueTask<U>>, T[]) to test if two trees are syntactically equal.
static bool Equals(this Expr left, Expr right)
=> left.ZipFold<Expr, bool>(
right,
(xs, results) =>
{
switch (xs[0])
{
case Add a1 when xs[1] is Add a2:
return results.All(x => x);
case Lit l1 when xs[1] is Lit l2:
return l1.Value == l2.Value;
default:
return false;
}
}
);
See Also
ZipFold<U>(XmlNode[], Func<XmlNode[], IEnumerable<U>, U>)
Flatten all of the nodes in the trees represented by values
into a single value at the same time, using an aggregation function to combine
nodes with the results of aggregating their children.
The trees are iterated in lock-step, much like an n-ary
Zip<TFirst,TSecond,TResult>(IEnumerable<TFirst>, IEnumerable<TSecond>, Func<TFirst,TSecond,TResult>).
When trees are not the same size, the larger ones are truncated both horizontally and vertically. That is, if a pair of nodes have a different number of children, the rightmost children of the larger of the two nodes are discarded.
Declaration
public static U ZipFold<U>(this XmlNode[] values, Func<XmlNode[], IEnumerable<U>, U> func)
Parameters
Type | Name | Description |
---|---|---|
XmlNode[] |
values |
The trees to fold |
Func<XmlNode[], IEnumerable<U>, U> |
func |
The aggregation function |
Returns
Type | Description |
---|---|
U |
The result of aggregating the two trees |
Type Parameters
Name | Description |
---|---|
U |
Examples
Here's an example of using ZipFold<T, U>(IRewriter<T>, Func<T[], IEnumerable<U>, U>, T[]) to test if two trees are syntactically equal.
static bool Equals(this Expr left, Expr right)
=> left.ZipFold<Expr, bool>(
right,
(xs, results) =>
{
switch (xs[0])
{
case Add a1 when xs[1] is Add a2:
return results.All(x => x);
case Lit l1 when xs[1] is Lit l2:
return l1.Value == l2.Value;
default:
return false;
}
}
);