// 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; } }