NetBeans Parsing API
Parsing API defines contract between parsers registerred for diferent languages and the rest of IDE. It is language neutral and it supports language embeddings.
The basic Parsing API constructs are Source, Snapshot and Embedding. Source identificates some concrete file or document. There is always at most one Source for one FileObject. Snapshot represents some concrete content of Source, and it is immutable. And Embedding represents some part of Snapshot written in different language. Embedding can contain some virtual code that is not contained in outside language too. Content of Embedding is represented by Snapshot and it can contain another Embeddings, so embeddings are recursive.
Parser is represented by abstract class called Parser. The only way how to register a new parser for some concrete mime type is to implement ParserFactory, and register it in your manifest file in folder called "Editors/" + mimeType. A new instance of Parser is always created for some concrete Snapshot, or collection of Snapshots. One instance of parser can be reused for more Snapshots created from the same Source. Result of parsing (AST, syntax errors, semantic information) is represented by Parser.Result class. Parser creates a new instance of Parser.Result for each Task. Following example shows how to integrate parser to the NetBeans using Parsing API:
class FooParserFactory extends ParserFactory {
public Parser createParser (
Collection<Snapshot> snapshots
) {
return new FooParser ();
}
private static class FooParser extends Parser {
private boolean cancelled = false;
private AST ast;
public void parse (
Snapshot snapshot,
Task task,
SchedulerEvent event
) throws ParseException {
cancelled = false;
for (...) {
// parsing snapshot.getText ();
if (cancelled) return;
}
ast = ...;
}
public abstract Result getResult (
Task task,
SchedulerEvent event
) throws ParseException {
return new FooResult (ast);
}
public void cancel () {
cancelled = true;
}
public void addChangeListener (
ChangeListener changeListener
) {
}
public void removeChangeListener (
ChangeListener changeListener
) {
}
}
public static class FooResult extends Result {
private AST ast;
private boolean valid = true;
FooResult (
AST ast
) {
this.ast = ast;
}
public AST getAST () {
if (!valid) throw new InvalidResultException ();
return ast;
}
public void invalidate () {
valid = false;
ast = null;
}
}
}
Parsing API defines two basic kinds of Tasks. High priority UserTasks and SchedulerTasks.
Execution of UserTask is synchnonous and it stops execution of all other tasks. There are two types of UserTask. Simple UserTask that supports some computations based on one block code written in one language. And MultiLanguageUserTask (???) that supports scanning of all blocks of code written in different languages embedded in one Source.
User of Parsing API can register various implementations of SchedulerTask for any language. Each SchedulerTask is registered for some specific Scheduler. Scheduler defines when task shoud be started (for example when current editor is changed, when some nodes are selected in Project View, or when cursor position is changed).
There are two specific tasks that can define language embedding. So task implementator can recognize blocks of embedded languages. Parsing API contains support for high priority ParsingAPI
Use Cases
- Parsing API Client can always ask for parser result.
This direct call has top level priority (stops all other requests).
Its designed to support direct user to IDE interaction
(like request for code completion) that have to be as
fast as possible. Such type of task is called UserTask.
This approach is used not only for parsing of file currently
edited in Editor, but it supports Refactoring too. - Parsing API Implementation listens on various changes in IDE
(typing in the current document, cursor movements, switching
of editor tabs, changes of classpath). These changes affects parse
tree of the current document, and various visualisation features
based on parser results (text coloring, hints, error stripe).
Parsing API Client can register some tasks that should run when
such change is done (Task). Implemementation of Parsing API than
calls all such registerred tasks. These calls has lower priority than
UserTasks, and they should not block user interaction with IDE
(typing in editor). - Parsing API defines how to recognize embedded languages. It should
be possible to create snapshot of some file, call some recognizers that
finds blocks of embedded languages and run different parsers for embedded blocks.
It should be able to use the same parser for top level language and any
embedded block of code written in the same language. Embedding recognizer
can add some virtual code (code that does not exists in top level source)
to the embedded block. There should be some support for translation
of offsets between top level source and virtual source generated
for embedded piece of code.