In This Article

Custom Data

The IParserState object passed to each callback in the parser framework has a property on it where custom data can be tracked and updated throughout each parsing operation.

Anything can be placed in this custom data store. It could be used to help perform semantic validation, construct name tables, and more.

Usage Scenarios

Although there are many things that can be tracked in custom data, here are a couple examples.

XML Element Hierarchy Validation

In the advanced XML syntax language in the Web Languages Add-on we maintain a stack of elements in this custom data. Any time we enter a new element, we push it on the stack. When we exit an element we pop it off. If the end tag name doesn't match the start tag name, we can intelligently report errors.

Name Tables

Many parser implementations may wish to construct a name table. The table could be stored in the custom data and then updated as a new type or variable name is encountered.

Bad Ways to Track Custom Data

One way you may be tempted to track custom data during a parse is to put a member variable on your grammar class. You would initialize it at some point and updated it in your callbacks.

The problem is that if more than one document is using the language, the grammar is potentially operating on multiple documents at the same time. This of course means that each parser working on the documents could be modifying the member variable while the other is working with the variable, thus leading to invalid data and possible exceptions.

The use of member variables is not recommended and for this reason, we recommend the use of techniques described below to store and update custom data instead.

Initializing Custom Data

The IParserState.CustomData property is the right place to store custom data for a parsing operation since it and its containing IParserState are specific to each parse request. The IParserState object for a parsing operation is created interally by the LL(*) Parser Framework and then is passed to each callback in your grammar. This begs the question, how can the custom data be initialized at the parsing operation start?

We recommend initializing the custom data in an initialize callback of the root production since that is the very first part of your grammar to be entered.

This example show how to add initialize and complete callbacks to the root production. Note that ellipses (...) indicate where the normal production terms and tree constructors would go.

this.Root.Production = (...).OnInitialize(CompilationUnitInitialize).OnComplete(CompilationUnitComplete);

A CompilationUnitInitialize implementation could look like this, where MyParserContext is a class with various properties to help track custom data for this particular parsing operation:

private void CompilationUnitInitialize(IParserState state) {
	// Initialize the state's custom data with a parser context
	state.CustomData = new MyParserContext();
}

Updating the Custom Data While Parsing

Any callback has access to the IParserState.CustomData property, and therefore can access the object created during initialization.

Properties on the object can be updated as needed. If the custom data is tracking a name table, any variable declarations should add an entry into the name table.

A stack property can be added to the custom data object so that you can track scopes entered/exited as code is parsed. That way you can know what sort of scope you are in when each callback is called. A stack item would be pushed onto the stack by you from a callback whenever a scope is entered, and then popped when the scope is existed.

These are just a couple ideas. Really the custom data can be used to track anything while the parsing operation is active.

Performing Semantic Validation with Custom Data

We've seen how to initialize the custom data for a parsing operation. The corresponding complete callback for the root production is an ideal place to examine the custom data to see if any further syntax or semantic errors should be reported.

For instance, in XML you may wish to report if not all tags were closed properly, assuming the custom data had data in it to indicate this scenario.

A CompilationUnitComplete implementation could look like this:

private void CompilationUnitComplete(IParserState state) {
	MyParserContext context = state.CustomData as MyParserContext;
	if (context != null) {
		// Report any semantic errors here with state.ReportError() based on data in the parser context
	}
}

Returning and Consuming the Custom Data

Since the LL(*) Parser Framework is being used, the IParser for the language should inherit LLParserBase. This base class has a virtual CreateParseData method that creates the IParseData returned by the parser.

By creating a language-specific IParseData object, you can get data from your CustomData object, store it in the IParseData, and it will be returned to the document. Thus it is available to consume by other language services such as IntelliPrompt since it is part of the parse data.

This code sample shows a possible implementation of a CreateParseData override that moves data from the MyParserContext object to the MyParseData object that is returned to the document:

protected override IParseData CreateParseData(IParseRequest request, IParserState state) {
	if (request == null)
		throw new ArgumentNullException("request");
	if (state == null)
		throw new ArgumentNullException("state");

	MyParseData parseData = new MyParseData();
			
	// Initialize
	this.InitializeParseData(parseData, state);

	// Pass on parser context data
	MyParserContext context = state.CustomData as MyParserContext;
	if (context != null)
		parseData.SomeData = context.SomeData;

	return parseData;
}