在.NET中执行简单的域特定语言

时间:2013-11-19 04:05:20

标签: c# .net dsl

我有一个非常简单的域特定语言,包含以下BNF定义

<Statement> ::= <Expression> | <Expression> <BinaryOperator> <Expression>
<Expression> ::= <Field> <Comparer> <Value> | "(" <Expresion> ")"
<Field> ::= "Name" | "Date of Birth" | "Address"
<Comparer> ::= "One Of", "=", "!=", "Not One Of"
<Value> ::= --Any string literal
<BinaryOperator> ::= "OR" | "AND"

我需要一个简单的指南来编写.NET框架中的DSL解析器/执行器。我看过Irony,但似乎生成代码而不是用值执行它。

一个例子是:

(Name = "Dave" AND Address = "150 nosuchstreet, nowhere") OR Date Of Birth != 15/10/1976

它基本上只需要评估语句(在将NAme,Address和出生日期的值替换为实际值之后)并返回布尔值true或false

2 个答案:

答案 0 :(得分:2)

作为练习,我已经快速为您的语言勾画出一个递归下降的解析器/评估器。我省略了最明显的代码(如标记化和字段值检索),我没有注意效率。

在您的实施中,您可能需要考虑

  • 远离字符串文字以获取字段名称和运算符名称,以减少字符串比较次数
  • 修改GetNextToken()以避免调用StartsWith()
  • 存储已解析的表达式树以重复用于多次计算
  • 支持短路评估(0 && x == 01 || x == 1

代码低于

public class Evaluator
{
    public enum TokenType
    {
        Field,
        Comparer,
        Value,
        Operator,
        Parenthesis
    }

    public class Token
    {
        public TokenType TokenType;
        public string Value;
    }

    private string statement;
    private int cursor = 0;
    private Token curToken;
    private object target;

    public Evaluator(string statement, object target)
    {
        this.statement = statement;
    }

    public bool EvaluateStatement()
    {
        GetNextToken();
        bool value = EvaluateExpression();
        if (curToken != null && curToken.TokenType == TokenType.Operator)
        {
            var op = curToken;
            GetNextToken();
            var v2 = EvaluateExpression();
            if (op.Value == "AND")
                return value && v2;
            else
                return value || v2;
        }

        return value;
    }

    private bool EvaluateExpression()
    {
        if (curToken.TokenType == TokenType.Parenthesis)
        {
            GetNextToken();
            bool value = EvaluateExpression();
            GetNextToken(); // skip closing parenthesis
            return value;
        }
        var fieldName = curToken.Value;
        GetNextToken();
        var op = curToken.Value;
        GetNextToken();
        var targetValue = curToken.Value;
        GetNextToken();

        var fieldValue = GetFieldValue(target, fieldName);
        return EvaluateComparer(fieldValue, targetValue, op);
    }

    private bool EvaluateComparer(string left, string right, string op)
    {
        if (op == "=")
        {
            return left == right;
        }
        else if (op == "!=")
        {
            return left != right;
        }
        // add more ops here
        else
        {
            throw new Exception("Invalid op");
        }
    }

    /// <summary>
    /// Get the next token from the input string, put it into 'curToken' and advance 'cursor'.
    /// </summary>
    public void GetNextToken()
    {
        // skip spaces
        while (cursor < statement.Length || Char.IsWhiteSpace(statement[cursor]))
        {
            cursor++;
        }

        if (cursor >= statement.Length)
        {
            curToken = null;
        }

        var remainder = statement.Substring(cursor);
        if (remainder.StartsWith("Name"))
        {
            cursor += "Name".Length;
            curToken = new Token
            {
                TokenType = TokenType.Field,
                Value = "Name"
            };
        }
        else if (remainder.StartsWith("!="))
        {
            cursor += "!=".Length;
            curToken = new Token
            {
                TokenType = TokenType.Field,
                Value = "!="
            };
        }
        // etc.
        else
        {
            throw new Exception("Unexpected token");
        }
    }

    private string GetFieldValue(object target, string fieldName)
    {
        // trivial to implement with fixed set of field names
        throw new NotImplementedException();
    }
}

答案 1 :(得分:1)

这是你如何在NLT中完成的。

扫描

"Name" -> NAME;
"Date of Birth" -> BIRTH;
"Address" -> ADDRESS;
// ... and so on

解析

S -> s:Statement { s };

Statement -> e:Expression { e }
           | e1:Expression AND e2:Expression { e1 && e2 }
           | e1:Expression OR e2:Expression { e1 || e2 }
           ;

Expression bool -> f:Field c:Comparer v:VALUE { compare(c,f,v) }
                 | LPAREN e:Expresion RPAREN { e }
                 ;

Field string -> f:[NAME BIRTH ADDRESS] { f };

Comparer string -> c:[ONEOF EQ NEQ NONEOF] { c };

你需要添加的只是函数compare来进行比较。结果,您将获得null(错误输入)或true / false值作为评估。