#nullable disable // Consider removing in the future to minimize likelihood of NullReferenceException; refer https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references
using System;
using System.Collections.Generic;
namespace GitHub.Actions.Expressions.Data
{
public static class ExpressionDataExtensions
{
public static ArrayExpressionData AssertArray(
this ExpressionData value,
String objectDescription)
{
if (value is ArrayExpressionData array)
{
return array;
}
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(ArrayExpressionData)}' was expected.");
}
public static DictionaryExpressionData AssertDictionary(
this ExpressionData value,
String objectDescription)
{
if (value is DictionaryExpressionData dictionary)
{
return dictionary;
}
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(DictionaryExpressionData)}' was expected.");
}
public static StringExpressionData AssertString(
this ExpressionData value,
String objectDescription)
{
if (value is StringExpressionData str)
{
return str;
}
throw new ArgumentException($"Unexpected type '{value?.GetType().Name}' encountered while reading '{objectDescription}'. The type '{nameof(StringExpressionData)}' was expected.");
}
///
/// Returns all context data objects (depth first)
///
public static IEnumerable Traverse(this ExpressionData value)
{
return Traverse(value, omitKeys: false);
}
///
/// Returns all context data objects (depth first)
///
/// If true, dictionary keys are omitted
public static IEnumerable Traverse(
this ExpressionData value,
Boolean omitKeys)
{
yield return value;
if (value is ArrayExpressionData || value is DictionaryExpressionData)
{
var state = new TraversalState(null, value);
while (state != null)
{
if (state.MoveNext(omitKeys))
{
value = state.Current;
yield return value;
if (value is ArrayExpressionData || value is DictionaryExpressionData)
{
state = new TraversalState(state, value);
}
}
else
{
state = state.Parent;
}
}
}
}
private sealed class TraversalState
{
public TraversalState(
TraversalState parent,
ExpressionData data)
{
Parent = parent;
m_data = data;
}
public Boolean MoveNext(Boolean omitKeys)
{
switch (m_data.Type)
{
case ExpressionDataType.Array:
var array = m_data.AssertArray("array");
if (++m_index < array.Count)
{
Current = array[m_index];
return true;
}
else
{
Current = null;
return false;
}
case ExpressionDataType.Dictionary:
var dictionary = m_data.AssertDictionary("dictionary");
// Return the value
if (m_isKey)
{
m_isKey = false;
Current = dictionary[m_index].Value;
return true;
}
if (++m_index < dictionary.Count)
{
// Skip the key, return the value
if (omitKeys)
{
m_isKey = false;
Current = dictionary[m_index].Value;
return true;
}
// Return the key
m_isKey = true;
Current = new StringExpressionData(dictionary[m_index].Key);
return true;
}
Current = null;
return false;
default:
throw new NotSupportedException($"Unexpected {nameof(ExpressionData)} type '{m_data.Type}'");
}
}
private ExpressionData m_data;
private Int32 m_index = -1;
private Boolean m_isKey;
public ExpressionData Current;
public TraversalState Parent;
}
}
}