Archive

Posts Tagged ‘Statements’

Creating custom Statements

November 3rd, 2009 JDT No comments

Abstract

In which the author outlines the method used in Calidus to parse a set of tokens into a new type of statement.

Concrete
Creating a new statement in Calidus is simple. A lot of utilities, methods and classes are available to make creating a statement a breeze.

Creating the basic classes

The first thing to do is to make sure that all parts are in place. The statement class itself must be declared. This class is derived from the AccessModifierStatement because an indexer can have an access modifier, and the base AccessModifierStatement class provides a few methods to work with these access modifiers.

/// <summary>
/// This class represents an indexer statement
/// </summary>
public class IndexerStatement : AccessModifierStatement
{
    /// <summary>
    /// Create a new instance of this class
    /// </summary>
    /// <param name="tokens">The list of tokens in the statement</param>
    public IndexerStatement(IEnumerable<TokenBase> tokens)
        : base(tokens)
    {
    }
}

The basic code for the statement factory class is also needed. In order to make this parsing as easy as possible the factory is derived from the FluentStatementFactory class to gain access to statement expressions. These statement expressions provide a fluent interface to declare the requirements for a statement.

/// <summary>
/// This class creates indexer statements
/// </summary>
public class IndexerStatementFactory : FluentStatementFactory<IndexerStatement>
{
    protected override IndexerStatement BuildStatement(IEnumerable<TokenBase> input)
    {
        return new IndexerStatement(input);
    }

    protected override bool IsValidContext(IStatementContext context)
    {
        return false;
    }

    protected override IStatementExpression Expression
    {
        get
        {
            StatementExpression expression = new StatementExpression();
            return expression;
        }
    }
}

Since Calidus is test-driven, a set of tests are needed to indicate that the statement is parsed correctly. For a factory, define a base test class that derives from CalidusTestBase. This class provides a set of utility methods and classes such as a TokenCreator and a StatementCreator that can be used by Calidus tests.

The TDD aficionado’s will notice that this is technically not a valid test: an additional unit test should be created to validate the context checking separately. However, this test already checks that the context is called through the mock repository in the VerifyAll-method. An extra test provides no additional advantage here.

[TestFixture]
public class IndexerStatementFactoryTest : CalidusTestBase
{
    [SetUp]
    public override void SetUp()
    {
        base.SetUp();
    }
}

Next, define a series of tests to indicate what is needed, and in this case a single test will suffice. Some additional information is needed to make this test work, so the basic frame of the test class must be expanded to suit the requirements for mocking.

[TestFixture]
public class IndexerStatementFactoryTest : CalidusTestBase
{
    private IndexerStatementFactory _factory;
    private IStatementContext _context;
    private MockRepository _mocker;

    [SetUp]
    public override void SetUp()
    {
        base.SetUp();
        _factory = new IndexerStatementFactory();
        _mocker = new MockRepository();
        _context = _mocker.DynamicMock<IStatementContext>();
    }

    [Test]
    public void FactoryShouldCreateStatementFromThisFollowedBySquareBracketInClass()
    {
        Expect.Call(_context.Parents).Return(new[] { new StatementParent(StatementCreator.CreateClassStatement(), StatementCreator.CreateOpenBlockStatement()) }).Repeat.Once();

        IList<TokenBase> input = new List<TokenBase>();
        input.Add(TokenCreator.Create<PublicModifierToken>());
        input.Add(TokenCreator.Create<SpaceToken>());
        input.Add(TokenCreator.Create<IdentifierToken>("String"));
        input.Add(TokenCreator.Create<SpaceToken>());
        input.Add(TokenCreator.Create<ThisToken>());
        input.Add(TokenCreator.Create<OpenSquareBracketToken>());
        input.Add(TokenCreator.Create<IntToken>());
        input.Add(TokenCreator.Create<SpaceToken>());
        input.Add(TokenCreator.Create<IdentifierToken>("index"));
        input.Add(TokenCreator.Create<CloseSquareBracketToken>());

        _mocker.ReplayAll();
        Assert.IsTrue(_factory.CanCreateStatementFrom(input, _context));
        _mocker.VerifyAll();
    }
}

This test code defines a mock statement context that indicates that the list of tokens to be parsed by the factory was found inside a class. The token list to parse is also created here.

Making it work

The following code is fairly simple as there are only two requirements for an indexer statement. First of all, it is part of a class: it can only be defined where the parent of the statement is the scope of a class. Second, an indexer statement consists of the This-keyword followed by a square bracket open. Both requirements can be expressed in code.

protected override bool IsValidContext(IStatementContext context)
{
    return context.Parents.FirstParentIsOfType<ClassStatement>();
}

protected override IStatementExpression Expression
{
    get
    {
        StatementExpression expression = new StatementExpression();
        expression.Contains<ThisToken>()
             .FollowedBy<OpenSquareBracketToken>();
        return expression;
    }
}

Combining these two pieces of codes validates both the context and the tokens, and running the unit tests with this code now yields a nice green bar.

Categories: Various Tags: ,

The Calidus Model

October 19th, 2009 JDT No comments

Abstract

In which the author outlines the basics of Calidus, the most important concepts and classes and how to use them.

Concrete

Calidus is not a terribly complex application. There are no zillion classes to get to know, there are no hard words or hidden meanings and most things do exactly what they sound like. In order to take full advantage of Calidus and develop custom parts such as rules it is important to know some of the inner workings of Calidus. This part is intended for advanced users and is a must-read for developers.

Characters

Technically, the smallest part of Calidus is the character, provided by the builtin character class. The concept of a character is not explicitly present in Calidus because no rule validation is performed on characters, but since they are the base element on which a token is built they deserve an honorable mention.

Tokens

A token is the smallest usefull part of the Calidus model. A token can be described as a collection containing at least one character, grouped together in the builtin String class. A token is either:

  • A single-character token, such as a SpaceToken or a TabToken
  • A single string token, such as an IdentifierToken or a NameSpaceToken
  • A multiple string token, such as a LineCommentToken

Calidus uses Coco/R to parse source code into tokens. This means that the list of tokens recognized by Calidus is almost exactly the same as the list of tokens recognized by Coco/R. There are some exceptions to this rule: whitespace is not normally part of the Coco/R parser’s returned token types and is therefore provided by an additional parser.

Tokens that cannot be parsed as a more specific token are GenericTokens.

Statements

A statement is a collection containing at least one token. What exactly constitutes a statement is somewhat determined by the C# language: in most cases the end of a statement is a semicolon. For if, for, while constructs and the likes the end of a statement is the closing round bracket ‘)’, and for attributes the end of a statement is the closing square bracket ‘]’.

The statement parser groups tokens together by splitting them at certain well-defined places, the most common example of this is when a semicolon was encountered. There are other ways to group statements, which include using brackets, newlines and other special token types.

Parsing token groups to see if they match a statement definition is a complex operation, and the definition of what constitutes a statement can differ greatly.  One thing that creates a lot of complexity is the amount of whitespace separating tokens, which may or may not be relevant depending on the context. To make token parsing easy Calidus provides a fluent interface that allows a declarative statement definition.

Statements that cannot be parsed as a more specific statement are GenericStatements.

Lines

Lines are probably the easiest elements in the Calidus model. A line is just a line: it is a collection of tokens that reside on the same line. Lines are the only elements that can have rules associated with them but do not have any native support for pluggable definitions and extensions: what constitutes a line can never change and is fixed by Calidus.

Blocks

Blocks are logical groupings of at least one statement. Blocks have very wide definitions: a series of using statements in a file are grouped into a block because they are related, but so are all elements in the source file since they compose the FileBlock.  The entire contents of a class constitutes a ClassBlock, and blocks are also the place where an if, while, for, do while, foreach… statement is present along with the code that is run as part of it.

Rules

Calidus is a source style validation tool, and the constraints placed on the source are coded into rules. Rules can be put on top of the following Calidus elements:

  • Statements, based off of StatementRuleBase
  • Lines, based off of LineRuleBase
  • Blocks, based off of BlockRuleBase

Tokens do not have rules associated with them as a token itself cannot be invalid. For example, a line comment is parsed into a LineCommentToken, but validating this token must be done through the statement that contains this token which is the LineCommentStatement.

Calidus rules are automatically configured by the class reponsible for creating them, allowing a user to use a pre-built rule but apply some customization to it. A good example of this principle is the MemberNameMatchesPatternRule, where a class member’s name is validated against a regex pattern. The pattern itself has a default setting, which can be adjusted to meet the developers needs.  This is unlike Microsoft’s StyleCop tool, which has several rules used to validate member names, each of which must be enabled or disabled and might not even meet the requirements when a naming pattern is used that is not built into the application. Rule configuration can be done through the main Calidus GUI.

Categories: Various Tags: , , , , ,