// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
namespace UnicodeTrieGenerator.StateAutomation;
///
/// Defines an AST node.
///
internal interface INode : IEnumerable
{
///
/// Gets the following position.
///
HashSet FollowPos { get; }
///
/// Gets a value indicating whether this node is nullable.
///
bool Nullable { get; }
///
/// Gets the number of child nodes in this node.
///
int Count { get; }
///
/// Gets or sets the node at the given position.
///
/// The index of the node.
/// The node at the given position.
INode this[int index] { get; set; }
///
/// Calculates the follow position for this instance.
///
void CalcFollowPos();
///
/// Returns a copy of the node.
///
/// The .
INode Copy();
}
///
/// Defines a logical AST node.
///
internal interface ILogicalNode : INode
{
///
/// Gets the collection of nodes as the first position.
///
HashSet FirstPos { get; }
///
/// Gets the collection of nodes at the last position.
///
HashSet LastPos { get; }
}
///
/// The base AST node.
///
internal abstract class Node : INode
{
protected List Enumerator { get; } = new();
///
public HashSet FollowPos { get; } = new();
///
public virtual bool Nullable => false;
public int Count => this.Enumerator.Count;
public INode this[int index]
{
get => this.Enumerator[index];
set => this.Enumerator[index] = value;
}
///
public virtual void CalcFollowPos()
{
foreach (INode node in this)
{
node.CalcFollowPos();
}
}
///
public abstract INode Copy();
public IEnumerator GetEnumerator() => this.Enumerator.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
///
/// Represents a variable reference.
///
internal class Variable : Node, ILogicalNode
{
public Variable(string name) => this.Name = name;
public string Name { get; }
///
HashSet ILogicalNode.FirstPos { get; } = new HashSet();
///
HashSet ILogicalNode.LastPos { get; } = new HashSet();
///
public override INode Copy() => new Variable(this.Name);
}
///
/// Represents a comment.
///
internal class Comment : Node
{
public Comment(string value) => this.Value = value;
public string Value { get; }
///
public override INode Copy() => new Comment(this.Value);
}
///
/// Represents an assignment statement. e.g. `variable = expression;`
///
internal class Assignment : Node
{
public Assignment(Variable variable, ILogicalNode expression)
{
this.Enumerator.Add(variable);
this.Enumerator.Add(expression);
}
public Variable Variable => (Variable)this[0];
public ILogicalNode Expression => (ILogicalNode)this[1];
///
public override INode Copy() => new Assignment(this.Variable, this.Expression);
}
///
/// Represents an alternation. e.g. `a | b`
///
internal class Alternation : Node, ILogicalNode
{
public Alternation(ILogicalNode a, ILogicalNode b)
{
this.Enumerator.Add(a);
this.Enumerator.Add(b);
}
public ILogicalNode A => (ILogicalNode)this[0];
public ILogicalNode B => (ILogicalNode)this[1];
///
public override bool Nullable => this.A.Nullable || this.B.Nullable;
///
public HashSet FirstPos => NodeUtilities.Union(this.A.FirstPos, this.B.FirstPos);
///
public HashSet LastPos => NodeUtilities.Union(this.A.LastPos, this.B.LastPos);
///
public override INode Copy()
=> new Alternation((ILogicalNode)this.A.Copy(), (ILogicalNode)this.B.Copy());
}
///
/// Represents a concatenation, or chain. e.g. `a b c`
///
internal class Concatenation : Node, ILogicalNode
{
public Concatenation(ILogicalNode a, ILogicalNode b)
{
this.Enumerator.Add(a);
this.Enumerator.Add(b);
}
public ILogicalNode A => (ILogicalNode)this[0];
public ILogicalNode B => (ILogicalNode)this[1];
///
public override bool Nullable => this.A.Nullable && this.B.Nullable;
///
public HashSet FirstPos
{
get
{
HashSet s = this.A.FirstPos;
if (this.A.Nullable)
{
s = NodeUtilities.Union(s, this.B.FirstPos);
}
return s;
}
}
///
public HashSet LastPos
{
get
{
HashSet s = this.B.LastPos;
if (this.B.Nullable)
{
s = NodeUtilities.Union(s, this.A.LastPos);
}
return s;
}
}
///
public override void CalcFollowPos()
{
base.CalcFollowPos();
foreach (INode n in this.A.LastPos)
{
NodeUtilities.AddAll(n.FollowPos, this.B.FirstPos);
}
}
///
public override INode Copy()
=> new Concatenation((ILogicalNode)this.A.Copy(), (ILogicalNode)this.B.Copy());
}
///
/// Represents a repetition. e.g. `a+`, `b*`, or `c?`
///
internal class Repeat : Node, ILogicalNode
{
public Repeat(ILogicalNode expression, string op)
{
this.Enumerator.Add(expression);
this.Op = op;
}
public ILogicalNode Expression => (ILogicalNode)this[0];
public string Op { get; }
///
public override bool Nullable => this.Op is "*" or "?";
///
public HashSet FirstPos => this.Expression.FirstPos;
///
public HashSet LastPos => this.Expression.LastPos;
///
public override void CalcFollowPos()
{
base.CalcFollowPos();
if (this.Op is "*" or "+")
{
foreach (INode n in this.LastPos)
{
NodeUtilities.AddAll(n.FollowPos, this.FirstPos);
}
}
}
///
public override INode Copy()
=> new Repeat((ILogicalNode)this.Expression.Copy(), this.Op);
}
///
/// Base class for leaf nodes.
///
internal abstract class Leaf : Node, ILogicalNode
{
///
public HashSet FirstPos => new() { this };
///
public HashSet LastPos => new() { this };
}
///
/// Represents a literal value, e.g. a number.
///
internal class Literal : Leaf
{
public Literal(int value) => this.Value = value;
public int Value { get; }
///
public override INode Copy() => new Literal(this.Value);
}
///
/// Marks the end of an expression.
///
internal class EndMarker : Leaf
{
///
public override INode Copy() => throw new NotImplementedException();
}
///
/// Represents a tag e.g. `a:(a b)`.
///
internal class Tag : Leaf
{
public Tag(string value) => this.Name = value;
public string Name { get; }
public override bool Nullable => true;
///
public override INode Copy() => new Tag(this.Name);
}
internal static class NodeUtilities
{
///
/// Builds a repetition of the given expression.
///
/// The expression to repeat.
/// The minimum value to repeat.
/// The maximum number to repeat.
/// THe .
/// Thrown if is out of range.
public static ILogicalNode BuildRepetition(ILogicalNode expression, int min, double max = double.PositiveInfinity)
{
if (min < 0 || min > max)
{
throw new ArgumentOutOfRangeException(nameof(min), $"Invalid repetition range: {min} {max}");
}
ILogicalNode? result = null;
for (int i = 0; i < min; i++)
{
result = Concat(result, (ILogicalNode)expression.Copy());
}
if (max == double.PositiveInfinity)
{
result = Concat(result, new Repeat((ILogicalNode)expression.Copy(), "*"));
}
else
{
for (int i = min; i < max; i++)
{
result = Concat(result, new Repeat((ILogicalNode)expression.Copy(), "?"));
}
}
return result!;
}
///
/// Concatenates two nodes.
///
/// The first node.
/// The second node.
/// The combined .
public static ILogicalNode Concat(ILogicalNode? a, ILogicalNode b)
{
if (a is null)
{
return b;
}
return new Concatenation(a, b);
}
///
/// Creates a union of two node sequences.
///
/// The first node sequence.
/// The second node sequence.
/// The .
public static HashSet Union(HashSet a, HashSet b)
{
var s = new HashSet(a);
AddAll(s, b);
return s;
}
///
/// Adds all the elements from set to .
///
/// The first node sequence.
/// The second node sequence.
public static void AddAll(HashSet a, HashSet b)
{
foreach (INode n in b)
{
_ = a.Add(n);
}
}
///
/// Determines whether two sets are equal.
///
/// The first node sequence.
/// The second node sequence.
/// The
public static bool Equal(ICollection a, ICollection b)
{
if (a == b)
{
return true;
}
if (a.Count != b.Count)
{
return false;
}
foreach (INode x in a)
{
if (!b.Contains(x))
{
return false;
}
}
return true;
}
}