The "parser state" of a module in rubberduck can be one of several values:
//note: ordering of the members is important
public enum ParserState
{
/// <summary>
/// Parse was requested but hasn't started yet.
/// </summary>
Pending,
/// <summary>
/// Project references are being loaded into parser state.
/// </summary>
LoadingReference,
/// <summary>
/// Code from modified modules is being parsed.
/// </summary>
Parsing,
/// <summary>
/// Parse tree is waiting to be walked for identifier resolution.
/// </summary>
Parsed,
/// <summary>
/// Resolving identifier references.
/// </summary>
Resolving,
/// <summary>
/// Parser state is in sync with the actual code in the VBE.
/// </summary>
Ready,
/// <summary>
/// Parsing could not be completed for one or more modules.
/// </summary>
Error,
/// <summary>
/// Parsing completed, but identifier references could not be resolved for one or more modules.
/// </summary>
ResolverError,
}
Now, Rubberduck uses each modules' state, and determines what the "overall" state is - and then displays that value in a status bar.
The rules are:
- If all modules have the same state, overall state is that value.
- If any module is in an error state, overall state is that error value.
- Overall state can only be "ready" when all modules are "ready".
- Overall state can only be "parsed" when all modules are "parsed".
- Otherwise return the state of the most advanced non-ready module.
The code started simple, got complicated, then too simple, and now looks like this - basically it went back to "too complicated":
private static readonly ParserState[] States = Enum.GetValues(typeof(ParserState)).Cast<ParserState>().ToArray();
private ParserState EvaluateParserState()
{
var moduleStates = _moduleStates.Values.ToList();
var state = States.SingleOrDefault(value => moduleStates.All(ps => ps == value));
if (state != default(ParserState))
{
// if all modules are in the same state, we have our result.
Debug.WriteLine("ParserState evaluates to '{0}' (thread {1})", state, Thread.CurrentThread.ManagedThreadId);
return state;
}
// error state takes precedence over every other state
if (moduleStates.Any(ms => ms == ParserState.Error))
{
Debug.WriteLine("ParserState evaluates to '{0}' (thread {1})", ParserState.Error,
Thread.CurrentThread.ManagedThreadId);
return ParserState.Error;
}
if (moduleStates.Any(ms => ms == ParserState.ResolverError))
{
Debug.WriteLine("ParserState evaluates to '{0}' (thread {1})", ParserState.ResolverError,
Thread.CurrentThread.ManagedThreadId);
return ParserState.ResolverError;
}
// intermediate states are toggled when *any* module has them.
var result = moduleStates.Min();
if (moduleStates.Any(ms => ms == ParserState.Parsing))
{
result = ParserState.Parsing;
}
if (moduleStates.Any(ms => ms == ParserState.Resolving))
{
result = ParserState.Resolving;
}
if (result == ParserState.Ready && moduleStates.Any(item => item != ParserState.Ready))
{
result = moduleStates.Except(new[] {ParserState.Ready}).Max();
}
Debug.Assert(result != ParserState.Ready || moduleStates.All(item => item == ParserState.Ready));
Debug.WriteLine("ParserState evaluates to '{0}' (thread {1})", result,
Thread.CurrentThread.ManagedThreadId);
return result;
}
Does anything stick out? Could it be simplified? Any/all feedback welcome!
LoadingReferences
shouldn't be a member of that enum. And it needs aAcquiringDeclarations
state, for the "first pass" walk;ResolvingReferences
is really about the "second pass" that resolves identifier references to acquired declarations. – Mat's Mug♦ 2 days ago