In SharpDevelop 220.127.116.1111, I have rewritten the ParserService class.
There are lots of changes.
First, from the point of view of an AddIn implementing IParser:
- SharpDevelop will not create a single instance of your class, but one instance per file.
- Keep in mind that there might be concurrent parser runs for the same file. IParser implementations must be thread-safe.
- The ITextBuffer interface now provides a 'Version' property which allows comparing two versions of the same document and efficiently retrieving the changes between them.
Together, these changes allow for the implementation of incremental parsers. The parser instance can simply store the ITextBufferVersion of a the last run in a field and use it to detect the changes to the next version.
We do not plan to write replace our existing parsers with incremental parsers now - but we are working on an incremental XML parser. The XAML code completion support is already taking advantage of this; and once we re-implement XML code folding for SharpDevelop 4, it will likely use this incremental parser, too.
However, a Parser instance should never cache the ICompilationUnit or parts of it: it now is possible for a file to have multiple compilation units (one per project that contains it). See "Support for files shared between multiple projects" below.
Originally, I wanted to give a "no-concurrency guarantee" for IParser implementations, i.e. the ParserService would ensure that there is only a single parser run for each file. However, to implement this, a
per-file lock while calling into the parser was required. The main thread could wait for existing parser runs to finish while the parser implementation would wait for the main thread to run an invoked method -> deadlock.
In the end, I decided the IParser implementation should have the responsibility for this - if it needs to avoid concurrent execution, it should use a lock.
For someone using the ParserService:
- Methods dealing with assembly references have been moved into the new class 'AssemblyParserService'.
- The remaining methods are now documented, in particular regarding their thread-safety.
- All events are now raised on the main thread. This guarantees that events arrive in the correct order and makes consuming them easier.
- EnqueueForParsing has been renamed to BeginParse and now provides a future (Task<ParseInformation>) to allow waiting for the result. However, to avoid deadlocks, this should not be done by any thread the parser might be waiting for (especially the main thread).
- The ParseFile method does not necessarily parse the snapshot of the file you specify - it might parse a newer version instead (but never an older version). Unlike BeginParse().Wait(), ParseFile() is safe to call from the main thread.
- If a file hasn't changed, calling ParseFile is a no-op.
- The ParseInformation class has been made immutable. Support for 'ValidCompilationUnit' and 'DirtyCompilationUnit' has been removed.
- The GetParser() method allows retrieving the IParser instance for a specific file. This is useful in some special cases for using details of specific IParser implementations.
The API changes here are more limited. Most important is the change to ParseInformation: the existing concept of keeping an old but valid compilation unit during parse errors was dropped because the was no useful upper bound on the age of the valid compilation unit; in some cases the 'valid compilation unit' might be several hours old and would represent an empty file.
All CompilationUnit-properties on ParseInformation now return same value; the old properties will be marked [Obsolete].
If a parser wants to reuse information from old parse runs because its error recovery is not reliable enough, the parser itself now has to maintain this state and mix it into the new compilation units - doing this is much easier now due to 'one IParser instance per file'.Support for files shared between multiple projects
Also, there has been a major internal change that isn't apparent in the API:
A single file can now have multiple ParseInformation instances - one per project that contains the file. Previously, files used by multiple projects would show up in code completion only for one of the projects. Now the file will be parsed once for each project that contains it.
Because a single IParser instance is used for all these parse runs, it is possible for incremental parsers to avoid redundantly parsing the file. However, a separate ICompilationUnit must be produced for each run because it contains a pointer to the parent project content.