-
Notifications
You must be signed in to change notification settings - Fork 10
Contract example
The contract extension aims to syntactically declare a series of pre-conditions that must be met in order for a particular method to function properly, for example:
void foo(int bar)
{
contract()
{
bar > 10;
}
}
Clearly, for foo to work properly bar must be greater than 10. The first thing to notice is the format of Excess extensions: extension name (extension parameters) { extension body }, this is not the only format allowed (see the programmer's guide), but its the most traditional one. In the future parameters will be optional.
Now onto the process of creating the extension, the web interface provides the extension writer with two files: extension and transform. In the extension file the extension is declared and then implemented in transform, which is named as such because must of the work consists in transforming one type of syntax node into another. In this case we will be transforming simple expressions (bar > 10) into if statements that evaluate such expressions and throw expressions if the conditions aren't met.
But first extension declaration:
environment
.keyword("contract");
syntax
.extension("contract", ExtensionKind.Code, ProcessContract);
First we notify the environment that it should recognize "contract" as a keyword, this is for cosmetic reasons and does not influence the transformation process. As such, it will not be discussed in detail (please see the programmer's guide for more information about the environment).
Onto the fun part: we instruct the syntactical part of the pipeline to recognize a "contract" extension following the syntactical rules for extensions discussed before. We also indicate this extension will be found in code as opposed to declarations. Once found, we want the engine to transform the node using the custom function ProcessContract.
Such function will be located in the transform file and it is a typical Roslyn function where you get the original node and return a transformed version of such node. It is not the purpose of this sample to teach Roslyn, so we'll not go into details, you can see (and try, and change) the whole project here
SyntaxNode ProcessContract(SyntaxNode node, SyntacticalExtension extension)
This is the function's declaration, receiving the parameter extension which contains all the moving parts (name, parameters, body) for the extension. The code:
var block = extension.Body as BlockSyntax;
List<StatementSyntax> checks = new List<StatementSyntax>();
foreach (var st in block.Statements)
{
var stExpression = st as ExpressionStatementSyntax;
if (stExpression == null)
{
//td: error, contracts only support boolean expression
continue;
}
var contractCheck = IfStatementFromExpression(stExpression);
checks.Add(contractCheck);
}
return CSharp.Block(checks);
Which is, basically: make sure they're all expressions and replace them with this template:
static private StatementSyntax ContractCheck = CSharp.ParseStatement(@"
if (!(__condition))
throw new InvalidOperationException(""Breach of contract!!"");");
Et voila!, your extension is done. When debugging it in the web site it would look like this:
