Skip to content

Typesafe Grammars

Dávid Németi edited this page Sep 5, 2013 · 3 revisions

Now that we have eliminated the indices, let's try to make our grammar typesafe. To do that we have to use the generic, typesafe version of Sarcasm's bnfterms. These bnfterms have the domain type as generic parameter.

So let's define bnfterm's domain type, and to avoid confusion we also have to clarify the difference between the bnfterm's type and the bnfterm's domain type.

The bnfterm's type is simply its conventional type, which is a subclass of the Irony.Parsing.BnfTerm class.

The bnfterm's domain type is only valid for those bnfterms which types implement Sarcasm's IBnfiTerm interface. In this case the domain type of a bnfterm means the type of the AST value its AST builder creates.

A typesafe domain-grammar binding has a declaring type, a member type and a value type as generic parameters. The value type is the domain type of the bound bnfterm, the member type is the type of the member (property or field) in the domain to which the bnfterm is bound to, and the declaring type is the type of the declaring type of the member in the domain.

When a domain-grammar binding is typesafe, it assures that the value type matches the member type (or the value type is derived from the member type), and assures that the declaring type matches the domain type of the bnfterm at the left side of the grammar rule (see examples for better understanding).

Here is the improved version of the typeless domain-bound grammar, which is now a typesafe domain-bound grammar using Sarcasm typesafe domain-grammar bindings:

using System;
using Irony;
using Irony.Ast;
using Irony.Parsing;
using Sarcasm;
using Sarcasm.GrammarAst;
using Sarcasm.Parsing;

using D = MyExpression.DomainDefinitions;

namespace MyExpression
{
    public class TypesafeGrammar : Sarcasm.GrammarAst.Grammar<D.Expression>
    {
        public TypesafeGrammar()
            : base(new MyExpression.Domain())    // the grammar needs the domain specific settings while building the AST
        {
            var TerminalFactoryS = new TerminalFactoryS(this);

            // definitions of nonterminals
            
            var expression = new BnfiTermChoice<D.Expression>();
            var binaryExpression = new BnfiTermRecord<D.BinaryExpression>();
            var numberLiteral = new BnfiTermRecord<D.NumberLiteral>();
            var binaryOperator = new BnfiTermChoice<D.BinaryOperator>();

            // definitions of terminals

            var ADD_OP = TerminalFactoryS.CreateKeyTerm("+", D.BinaryOperator.Add);
            var SUB_OP = TerminalFactoryS.CreateKeyTerm("-", D.BinaryOperator.Sub);
            var MUL_OP = TerminalFactoryS.CreateKeyTerm("*", D.BinaryOperator.Mul);
            var DIV_OP = TerminalFactoryS.CreateKeyTerm("/", D.BinaryOperator.Div);
            var POW_OP = TerminalFactoryS.CreateKeyTerm("^", D.BinaryOperator.Pow);

            var LEFT_PAREN = TerminalFactoryS.CreateKeyTerm("(");
            var RIGHT_PAREN = TerminalFactoryS.CreateKeyTerm(")");

            // syntax rules

            // Types of bfnterms at the right side of this rule should be equal to or inherited from
            // the type of expression (D.Expression). Due to C# language limitations the SetRuleOr method
            // must be used instead of the Rule property in order to support inheritance.
            expression.SetRuleOr(
                binaryExpression,
                numberLiteral,
                LEFT_PAREN + expression + RIGHT_PAREN
                );

            binaryExpression.Rule =
                expression.BindTo(binaryExpression, t => t.Term1)
                + binaryOperator.BindTo(binaryExpression, t => t.Op)
                + expression.BindTo(binaryExpression, t => t.Term2)
                ;

            numberLiteral.Rule = TerminalFactoryS.CreateNumberLiteral().BindTo(numberLiteral, t => t.Value);

            binaryOperator.Rule = ADD_OP | SUB_OP | MUL_OP | DIV_OP | POW_OP;

            // operator precedences and associativities
            
            RegisterOperators(10, ADD_OP, SUB_OP);
            RegisterOperators(20, MUL_OP, DIV_OP);
            RegisterOperators(30, Associativity.Right, POW_OP);
        }
    }
}

Note that due to the typesafety the followings all result in compile time errors:

binaryExpression.Rule =
    // domain type of binaryOperator (D.BinaryOperator) does not match with the type of D.BinaryExpression.Term1 (D.Expression)
    binaryOperator.BindTo(binaryExpression, t => t.Term1)
    
    + binaryOperator.BindTo(binaryExpression, t => t.Op)
    + expression.BindTo(binaryExpression, t => t.Term2)
    ;

binaryExpression.Rule =
    // D.Expression does not have a member with name "Term1"
    expression.BindTo(expression, t => t.Term1)
    
    + binaryOperator.BindTo(binaryExpression, t => t.Op)
    + expression.BindTo(binaryExpression, t => t.Term2)
    ;

// declaring type of D.BinaryExpression.Term1 (D.BinaryExpression) does not match with
// the domain type of numberLiteral (D.NumberLiteral)
numberLiteral.Rule = TerminalFactoryS.CreateNumberLiteral().BindTo(binaryExpression, t => t.Term1);

expression.SetRuleOr(
    // domain type of binaryOperator (D.BinaryOperator) does not match with the domain type of expression (D.Expression)
    binaryOperator,
    
    binaryExpression,
    numberLiteral,
    LEFT_PAREN + expression + RIGHT_PAREN
    );

So we added typesafety as well. It's time to go into details about the certain bnfterm types in Sarcasm, which has been deliberately avoided in this documentation until now in order to concentrate on the main goals. If you are interested, continue with BnfTerms in Sarcasm.

Clone this wiki locally