This commit is contained in:
Jordan Wages 2026-06-24 03:34:35 -05:00
commit 4d6a0c6bab

View file

@ -4,32 +4,47 @@ using System.IO;
namespace CSMic
{
/// <summary> The interpreter that parses user input at runtime into strongly typed .Net values. </summary>
public class InputInterpreter
{
#region Members
/// <summary> The numeric value store. </summary>
private decimal numericValue = 0;
/// <summary> The string value store. </summary>
private string stringValue = string.Empty;
/// <summary> The time taken for the last entry the parser interpreted. </summary>
private TimeSpan lastExecutionTime = TimeSpan.Zero;
// Variable stores
#region Variable stores
/// <summary> (Immutable) The numeric variables. </summary>
private readonly Dictionary<string, decimal> numericVariables;
/// <summary> (Immutable) The numeric array variables. </summary>
private readonly Dictionary<string, decimal[]> numericArrayVariables;
private readonly Dictionary<string, string> expressionVariables;
/// <summary> (Immutable) The expression variables. </summary>
private readonly Dictionary<string, string> expressionVariables;
#endregion
// Function registry
/// <summary> (Immutable) The function registry. </summary>
private readonly Dictionary<string, ICodedFunction> functions;
// Tracks expression variables currently being evaluated to prevent recursion
/// <summary>
/// (Immutable) Tracks expression variables currently being evaluated to prevent recursion.
/// </summary>
private readonly List<string> evaluationStack;
// Shared recursion tracker across nested evaluations
private sealed class RecursionTracker { public int Hits; }
/// <summary> Shared recursion tracker used across nested evaluations. </summary>
private sealed class RecursionTracker {
/// <summary> The number of recursive trips detected. </summary>
public int Hits;
}
/// <summary> (Immutable) The recursion tracker. </summary>
private readonly RecursionTracker recursion;
#endregion
#region Constructors
/// <summary> Default constructor. </summary>
public InputInterpreter()
{
numericVariables = new Dictionary<string, decimal>(StringComparer.Ordinal);
@ -40,7 +55,8 @@ namespace CSMic
recursion = new RecursionTracker();
}
// Internal constructor to create a child interpreter that shares stores
/// <summary> Internal constructor to create a child interpreter that shares stores. </summary>
/// <param name="parent"> The parent <see cref="InputInterpreter"/>. </param>
internal InputInterpreter(InputInterpreter parent)
{
this.numericVariables = parent.numericVariables;
@ -57,6 +73,8 @@ namespace CSMic
#region Properties
/// <summary> Gets the numeric value. </summary>
/// <value> The numeric value. </value>
public decimal NumericValue
{
get
@ -65,6 +83,8 @@ namespace CSMic
}
}
/// <summary> Gets the string value. </summary>
/// <value> The string value. </value>
public string StringValue
{
get
@ -73,6 +93,8 @@ namespace CSMic
}
}
/// <summary> Gets the last execution time. </summary>
/// <value> The last execution time. </value>
public TimeSpan LastExecutionTime
{
get
@ -81,6 +103,8 @@ namespace CSMic
}
}
/// <summary> Gets the variables. </summary>
/// <value> The encapsulated variables. </value>
public IEnumerable<Variable> Variables
{
get
@ -95,12 +119,17 @@ namespace CSMic
#region Output Plumbing
/// <summary> Hydrates long-lived output variables for delayed access. </summary>
/// <param name="numericValue"> The numeric value store. </param>
/// <param name="stringValue"> The string value store. </param>
internal void ProduceOutput(decimal numericValue, string stringValue)
{
this.numericValue = numericValue;
this.stringValue = stringValue;
}
/// <summary> Hydrates long-lived output variables for delayed access. </summary>
/// <param name="functionValue"> The function value to be converted. </param>
internal void ProduceOutput(FunctionValue functionValue)
{
switch (functionValue.Type)
@ -126,12 +155,24 @@ namespace CSMic
#region Variable APIs
/// <summary> Attempts to get a numeric value from the variable store with a given name. </summary>
/// <param name="name"> The name. </param>
/// <param name="value"> [out] The value. </param>
/// <returns> True if it succeeds, false if it fails. </returns>
internal bool TryGetNumeric(string name, out decimal value)
=> numericVariables.TryGetValue(name, out value);
/// <summary> Attempts to get a numeric array from the variable store with a given name. </summary>
/// <param name="name"> The name. </param>
/// <param name="values"> [out] The values. </param>
/// <returns> True if it succeeds, false if it fails. </returns>
internal bool TryGetNumericArray(string name, out decimal[] values)
=> numericArrayVariables.TryGetValue(name, out values!);
/// <summary> Attempts to get an expression from the variable store with a given name. </summary>
/// <param name="name"> The name. </param>
/// <param name="expr"> [out] The expression. </param>
/// <returns> True if it succeeds, false if it fails. </returns>
internal bool TryGetExpression(string name, out string expr)
{
if (expressionVariables.TryGetValue(name, out expr!))
@ -142,9 +183,16 @@ namespace CSMic
return false;
}
// Recursion tracking helpers managed by the parser when evaluating expression variables
/// <summary>
/// Recursion tracking helpers managed by the parser when evaluating expression variables.
/// </summary>
/// <param name="name"> The name of the variable to check. </param>
/// <returns> True if currently evaluating, false if not. </returns>
internal bool IsEvaluating(string name) => evaluationStack.Contains(name);
/// <summary> Begins evaluating a named expression. </summary>
/// <param name="name"> The name of the expression variable. </param>
/// <returns> The current recurrsion depth. </returns>
internal int BeginEvaluating(string name)
{
int depth = evaluationStack.Count;
@ -152,6 +200,8 @@ namespace CSMic
return depth;
}
/// <summary> Ends evaluating a named expression. </summary>
/// <param name="depth"> The recurrsion depth. </param>
internal void EndEvaluating(int depth)
{
while (evaluationStack.Count > depth)
@ -160,46 +210,67 @@ namespace CSMic
}
}
// Recursion hit scoping across a single top-level expression evaluation
/// <summary> Recursion hit scoping across a single top-level expression evaluation. </summary>
/// <returns> An int. </returns>
internal int BeginRecursionScope()
{
return recursion.Hits;
}
// Returns true if recursion occurred within this scope
/// <summary> Returns true if recursion occurred within this scope. </summary>
/// <param name="startHits"> The initial recurrsive hit counter. </param>
/// <returns> True if recurrsion occurred within this scope, false otherwise. </returns>
internal bool EndRecursionScope(int startHits)
{
return recursion.Hits > startHits;
}
/// <summary> Mark a recursion hit. </summary>
internal void MarkRecursionHit()
{
recursion.Hits++;
}
/// <summary> Assign a numeric value to a variable. </summary>
/// <param name="name"> The name of the variable. </param>
/// <param name="value"> [out] The value. </param>
internal void AssignNumeric(string name, decimal value)
{
numericVariables[name] = value;
// Remove conflicting bindings
expressionVariables.Remove(name);
numericArrayVariables.Remove(name);
}
/// <summary> Assign a numeric array set to a variable. </summary>
/// <param name="name"> The name of the variable. </param>
/// <param name="values"> [out] The array values. </param>
internal void AssignNumericArray(string name, decimal[] values)
{
numericArrayVariables[name] = values;
// Remove conflicting bindings
numericVariables.Remove(name);
expressionVariables.Remove(name);
}
/// <summary> Assign an expression to a variable. </summary>
/// <param name="name"> The name of the variable. </param>
/// <param name="expressionText"> The expression text. </param>
internal void AssignExpression(string name, string expressionText)
{
expressionVariables[name] = expressionText;
// Remove conflicting numeric value
numericVariables.Remove(name);
numericArrayVariables.Remove(name);
}
#endregion
#region Expression Evaluation
/// <summary> Evaluates an expression. </summary>
/// <param name="expressionText"> The expression text. </param>
/// <returns> The result of the expression. </returns>
internal FunctionValue EvaluateExpression(string expressionText)
{
// Create a child interpreter sharing stores, so ProduceOutput doesn't affect parent state
@ -221,7 +292,10 @@ namespace CSMic
}
}
// Primary developer-facing API: interpret input and return numeric result
/// <summary> Interpret input and return a numeric result. </summary>
/// <param name="input"> The input text to parse. </param>
/// <returns> A decimal value representing the immediate result. </returns>
/// <remarks> This is the primary developer-facing API. </remarks>
public decimal Interpret(string input)
{
DateTime start = DateTime.Now;
@ -263,11 +337,24 @@ namespace CSMic
#region Functions
/// <summary> Registers a function described by <see cref="ICodedFunction"/> interface. </summary>
/// <param name="function"> The function. </param>
/// <seealso cref="FunctionArgument"/>
/// <seealso cref="FunctionValueType"/>
/// <seealso cref="FunctionValue"/>
public void RegisterFunction(ICodedFunction function)
{
functions[function.Name] = function;
}
/// <summary> Executes a named function. </summary>
/// <param name="name"> The name. </param>
/// <param name="args"> A variable-length parameters list containing arguments. </param>
/// <returns> The result of the function execution. </returns>
/// <seealso cref="FunctionArgument"/>
/// <seealso cref="FunctionValueType"/>
/// <seealso cref="FunctionValue"/>
/// <seealso cref="ICodedFunction"/>
internal FunctionValue ExecuteFunction(string name, params FunctionArgument[] args)
{
if (functions.TryGetValue(name, out var fn))