Skip to content

Commit 1ae5631

Browse files
authored
Merge pull request #1817 from Hosch250/parserIssues
Make parser/resolver more async
2 parents c57052f + 237f51e commit 1ae5631

File tree

3 files changed

+124
-88
lines changed

3 files changed

+124
-88
lines changed

RetailCoder.VBE/Navigation/CodeExplorer/CodeExplorerViewModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,15 +244,15 @@ public ObservableCollection<CodeExplorerItemViewModel> Projects
244244
}
245245
}
246246

247-
private void ParserState_StateChanged(object sender, EventArgs e)
247+
private void ParserState_StateChanged(object sender, ParserStateEventArgs e)
248248
{
249249
if (Projects == null)
250250
{
251251
Projects = new ObservableCollection<CodeExplorerItemViewModel>();
252252
}
253253

254254
IsBusy = _state.Status < ParserState.ResolvedDeclarations;
255-
if (_state.Status != ParserState.ResolvedDeclarations)
255+
if (e.State != ParserState.ResolvedDeclarations)
256256
{
257257
return;
258258
}

Rubberduck.Parsing/VBA/RubberduckParser.cs

Lines changed: 116 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public class RubberduckParser : IRubberduckParser
3131
private readonly IDictionary<VBComponent, IDictionary<Tuple<string, DeclarationType>, Attributes>> _componentAttributes
3232
= new Dictionary<VBComponent, IDictionary<Tuple<string, DeclarationType>, Attributes>>();
3333

34-
3534
private readonly ReferencedDeclarationsCollector _comReflector;
3635

3736
private readonly VBE _vbe;
@@ -57,7 +56,6 @@ public RubberduckParser(
5756
state.ParseRequest += ReparseRequested;
5857
}
5958

60-
6159
private void ReparseRequested(object sender, ParseRequestEventArgs e)
6260
{
6361
if (e.IsFullReparseRequest)
@@ -72,10 +70,26 @@ private void ReparseRequested(object sender, ParseRequestEventArgs e)
7270
{
7371
ParseAsync(e.Component, CancellationToken.None).Wait();
7472

75-
if (_state.Status != ParserState.Error)
73+
if (_resolverTokenSource.IsCancellationRequested || _central.IsCancellationRequested)
74+
{
75+
return;
76+
}
77+
78+
if (_state.Status == ParserState.Error) { return; }
79+
80+
var qualifiedName = new QualifiedModuleName(e.Component);
81+
Logger.Debug("Module '{0}' {1}", qualifiedName.ComponentName,
82+
_state.IsNewOrModified(qualifiedName) ? "was modified" : "was NOT modified");
83+
84+
_state.SetModuleState(e.Component, ParserState.Resolving);
85+
ResolveDeclarations(qualifiedName.Component,
86+
_state.ParseTrees.Find(s => s.Key == qualifiedName).Value);
87+
88+
_state.SetStatusAndFireStateChanged(ParserState.ResolvedDeclarations);
89+
90+
if (_state.Status < ParserState.Error)
7691
{
77-
Logger.Trace("Starting resolver task");
78-
Resolve(_central.Token);
92+
ResolveReferencesAsync();
7993
}
8094
});
8195
}
@@ -103,6 +117,12 @@ public void Parse()
103117
}
104118
}
105119

120+
// tests do not fire events when components are removed--clear components
121+
foreach (var tree in _state.ParseTrees)
122+
{
123+
_state.ClearStateCache(tree.Key.Component);
124+
}
125+
106126
SyncComReferences(_state.Projects);
107127

108128
foreach (var component in components)
@@ -119,18 +139,42 @@ public void Parse()
119139
}
120140
}
121141

142+
_projectDeclarations.Clear();
143+
_state.ClearBuiltInReferences();
144+
122145
var parseTasks = new Task[components.Count];
123146
for (var i = 0; i < components.Count; i++)
124147
{
125-
parseTasks[i] = ParseAsync(components[i], CancellationToken.None);
148+
var index = i;
149+
parseTasks[i] = new Task(() =>
150+
{
151+
ParseAsync(components[index], CancellationToken.None).Wait();
152+
153+
if (_resolverTokenSource.IsCancellationRequested || _central.IsCancellationRequested)
154+
{
155+
return;
156+
}
157+
158+
if (_state.Status == ParserState.Error) { return; }
159+
160+
var qualifiedName = new QualifiedModuleName(components[index]);
161+
Logger.Debug("Module '{0}' {1}", qualifiedName.ComponentName,
162+
_state.IsNewOrModified(qualifiedName) ? "was modified" : "was NOT modified");
163+
164+
_state.SetModuleState(components[index], ParserState.Resolving);
165+
ResolveDeclarations(qualifiedName.Component,
166+
_state.ParseTrees.Find(s => s.Key == qualifiedName).Value);
167+
});
168+
169+
parseTasks[i].Start();
126170
}
127171

128172
Task.WaitAll(parseTasks);
173+
_state.SetStatusAndFireStateChanged(ParserState.ResolvedDeclarations);
129174

130-
if (_state.Status != ParserState.Error)
175+
if (_state.Status < ParserState.Error)
131176
{
132-
Logger.Trace("Starting resolver task");
133-
Resolve(_central.Token); // Tests expect this to be synchronous
177+
Task.WaitAll(ResolveReferencesAsync());
134178
}
135179
}
136180

@@ -179,8 +223,7 @@ private void ParseAll()
179223
State.SetStatusAndFireStateChanged(_state.Status);
180224
return;
181225
}
182-
183-
226+
184227
lock (_state) // note, method is invoked from UI thread... really need the lock here?
185228
{
186229
foreach (var component in toParse)
@@ -192,9 +235,6 @@ private void ParseAll()
192235
// note: seting to 'Parsed' would include them in the resolver walk. 'Ready' excludes them.
193236
_state.SetModuleState(component, ParserState.Ready);
194237
}
195-
196-
//Debug.Assert(unchanged.All(component => _state.GetModuleState(component) == ParserState.Ready));
197-
//Debug.Assert(toParse.All(component => _state.GetModuleState(component) == ParserState.Pending));
198238
}
199239

200240
// invalidation cleanup should go into ParseAsync?
@@ -206,19 +246,71 @@ private void ParseAll()
206246
}
207247
}
208248

209-
var parseTasks = new Task[components.Count];
210-
for (var i = 0; i < components.Count; i++)
249+
_projectDeclarations.Clear();
250+
_state.ClearBuiltInReferences();
251+
252+
var parseTasks = new Task[toParse.Count];
253+
for (var i = 0; i < toParse.Count; i++)
211254
{
212-
parseTasks[i] = ParseAsync(components[i], CancellationToken.None);
255+
var index = i;
256+
parseTasks[i] = new Task(() =>
257+
{
258+
ParseAsync(toParse[index], CancellationToken.None).Wait();
259+
260+
if (_resolverTokenSource.IsCancellationRequested || _central.IsCancellationRequested)
261+
{
262+
return;
263+
}
264+
265+
if (_state.Status == ParserState.Error) { return; }
266+
267+
var qualifiedName = new QualifiedModuleName(toParse[index]);
268+
Logger.Debug("Module '{0}' {1}", qualifiedName.ComponentName,
269+
_state.IsNewOrModified(qualifiedName) ? "was modified" : "was NOT modified");
270+
271+
_state.SetModuleState(toParse[index], ParserState.Resolving);
272+
ResolveDeclarations(qualifiedName.Component,
273+
_state.ParseTrees.Find(s => s.Key == qualifiedName).Value);
274+
});
275+
276+
parseTasks[i].Start();
213277
}
214278

215279
Task.WaitAll(parseTasks);
280+
_state.SetStatusAndFireStateChanged(ParserState.ResolvedDeclarations);
281+
282+
if (_state.Status < ParserState.Error)
283+
{
284+
ResolveReferencesAsync();
285+
}
286+
}
287+
288+
private Task[] ResolveReferencesAsync()
289+
{
290+
var finder = new DeclarationFinder(_state.AllDeclarations, _state.AllComments, _state.AllAnnotations);
291+
var passes = new List<ICompilationPass>
292+
{
293+
// This pass has to come first because the type binding resolution depends on it.
294+
new ProjectReferencePass(finder),
295+
new TypeHierarchyPass(finder, new VBAExpressionParser()),
296+
new TypeAnnotationPass(finder, new VBAExpressionParser())
297+
};
298+
passes.ForEach(p => p.Execute());
299+
300+
var tasks = new Task[_state.ParseTrees.Count];
216301

217-
if (_state.Status != ParserState.Error)
302+
for (var index = 0; index < _state.ParseTrees.Count; index++)
218303
{
219-
Logger.Trace("Starting resolver task");
220-
Resolve(_central.Token);
304+
var kvp = _state.ParseTrees[index];
305+
if (_resolverTokenSource.IsCancellationRequested || _central.IsCancellationRequested)
306+
{
307+
return new Task[0];
308+
}
309+
310+
tasks[index] = Task.Run(() => ResolveReferences(finder, kvp.Key.Component, kvp.Value));
221311
}
312+
313+
return tasks;
222314
}
223315

224316
private void AddBuiltInDeclarations(IReadOnlyList<VBProject> projects)
@@ -375,7 +467,7 @@ private void UnloadComReference(Reference reference, IReadOnlyList<VBProject> pr
375467
}
376468
}
377469

378-
public Task ParseAsync(VBComponent component, CancellationToken token, TokenStreamRewriter rewriter = null)
470+
private Task ParseAsync(VBComponent component, CancellationToken token, TokenStreamRewriter rewriter = null)
379471
{
380472
lock (_state)
381473
lock (component)
@@ -460,66 +552,7 @@ private void ParseAsyncInternal(VBComponent component, CancellationToken token,
460552
parser.Start(token);
461553
}
462554

463-
private void Resolve(CancellationToken token)
464-
{
465-
State.SetStatusAndFireStateChanged(ParserState.Resolving);
466-
var sharedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_resolverTokenSource.Token, token);
467-
// tests expect this to be synchronous :/
468-
//Task.Run(() => ResolveInternal(sharedTokenSource.Token));
469-
ResolveInternal(sharedTokenSource.Token);
470-
}
471-
472-
private void ResolveInternal(CancellationToken token)
473-
{
474-
var components = new List<VBComponent>();
475-
foreach (var project in _state.Projects)
476-
{
477-
if (project.Protection == vbext_ProjectProtection.vbext_pp_locked)
478-
{
479-
continue;
480-
}
481-
482-
foreach (VBComponent component in project.VBComponents)
483-
{
484-
components.Add(component);
485-
}
486-
}
487-
488-
if (!_state.HasAllParseTrees(components))
489-
{
490-
return;
491-
}
492-
_projectDeclarations.Clear();
493-
_state.ClearBuiltInReferences();
494-
foreach (var kvp in _state.ParseTrees)
495-
{
496-
var qualifiedName = kvp.Key;
497-
Logger.Debug("Module '{0}' {1}", qualifiedName.ComponentName, _state.IsNewOrModified(qualifiedName) ? "was modified" : "was NOT modified");
498-
// modified module; walk parse tree and re-acquire all declarations
499-
if (token.IsCancellationRequested) return;
500-
ResolveDeclarations(qualifiedName.Component, kvp.Value);
501-
}
502-
503-
_state.SetStatusAndFireStateChanged(ParserState.ResolvedDeclarations);
504-
505-
// walk all parse trees (modified or not) for identifier references
506-
var finder = new DeclarationFinder(_state.AllDeclarations, _state.AllComments, _state.AllAnnotations);
507-
var passes = new List<ICompilationPass>
508-
{
509-
// This pass has to come first because the type binding resolution depends on it.
510-
new ProjectReferencePass(finder),
511-
new TypeHierarchyPass(finder, new VBAExpressionParser()),
512-
new TypeAnnotationPass(finder, new VBAExpressionParser())
513-
};
514-
passes.ForEach(p => p.Execute());
515-
foreach (var kvp in _state.ParseTrees)
516-
{
517-
if (token.IsCancellationRequested) return;
518-
ResolveReferences(finder, kvp.Key.Component, kvp.Value);
519-
}
520-
}
521-
522-
private readonly Dictionary<string, Declaration> _projectDeclarations = new Dictionary<string, Declaration>();
555+
private readonly ConcurrentDictionary<string, Declaration> _projectDeclarations = new ConcurrentDictionary<string, Declaration>();
523556
private void ResolveDeclarations(VBComponent component, IParseTree tree)
524557
{
525558
if (component == null) { return; }
@@ -534,7 +567,7 @@ private void ResolveDeclarations(VBComponent component, IParseTree tree)
534567
if (!_projectDeclarations.TryGetValue(projectQualifiedName.ProjectId, out projectDeclaration))
535568
{
536569
projectDeclaration = CreateProjectDeclaration(projectQualifiedName, project);
537-
_projectDeclarations.Add(projectQualifiedName.ProjectId, projectDeclaration);
570+
_projectDeclarations.AddOrUpdate(projectQualifiedName.ProjectId, projectDeclaration, (s, c) => projectDeclaration);
538571
lock (_state)
539572
{
540573
_state.AddDeclaration(projectDeclaration);
@@ -584,7 +617,7 @@ private Declaration CreateProjectDeclaration(QualifiedModuleName projectQualifie
584617
private void ResolveReferences(DeclarationFinder finder, VBComponent component, IParseTree tree)
585618
{
586619
var state = _state.GetModuleState(component);
587-
if (_state.Status == ParserState.ResolverError || (state != ParserState.Parsed))
620+
if (state != ParserState.Resolving)
588621
{
589622
return;
590623
}

Rubberduck.Parsing/VBA/RubberduckParserState.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,8 @@ public void ClearStateCache(VBProject project, bool notifyStateChanged = false)
584584

585585
if (notifyStateChanged)
586586
{
587-
OnStateChanged();
587+
OnStateChanged(ParserState.ResolvedDeclarations); // trigger test explorer and code explorer updates
588+
OnStateChanged(ParserState.Ready); // trigger find all references &c. updates
588589
}
589590
}
590591

@@ -675,7 +676,8 @@ public bool ClearStateCache(VBComponent component, bool notifyStateChanged = fal
675676

676677
if (notifyStateChanged)
677678
{
678-
OnStateChanged();
679+
OnStateChanged(ParserState.ResolvedDeclarations); // trigger test explorer and code explorer updates
680+
OnStateChanged(ParserState.Ready); // trigger find all references &c. updates
679681
}
680682

681683
return success;
@@ -697,7 +699,8 @@ public bool RemoveRenamedComponent(VBComponent component, string oldComponentNam
697699

698700
if (success)
699701
{
700-
OnStateChanged();
702+
OnStateChanged(ParserState.ResolvedDeclarations); // trigger test explorer and code explorer updates
703+
OnStateChanged(ParserState.Ready); // trigger find all references &c. updates
701704
}
702705
return success;
703706
}

0 commit comments

Comments
 (0)