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

C# 在.NET中执行简单的特定于域的语言,c#,.net,dsl,C#,.net,Dsl,我有一种非常简单的领域特定语言,由以下BNF定义组成 <Statement> ::= <Expression> | <Expression> <BinaryOperator> <Expression> <Expression> ::= <Field> <Comparer> <Value> | "(" <Expresion> ")" <Field> ::= "Name

我有一种非常简单的领域特定语言,由以下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"

它基本上只需要对语句求值(在用实际值替换名称、地址和出生日期的值之后)并返回布尔值true或false作为练习,我已经为您的语言快速绘制了一个递归下降解析器/求值器。我省略了最明显的代码(比如标记化和字段值检索),也没有注意效率

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

  • 远离字段名和运算符名的字符串文字,以减少字符串比较的数量
  • 修改
    GetNextToken()
    以避免调用
    StartsWith()
  • 存储解析后的表达式树以重复用于多个计算
  • 支持短路评估(
    0&&x==0
    1 | | 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();
    }
}
公共类求值器
{
公共枚举令牌类型
{
领域
比较器,
价值
操作人员
括号
}
公共类令牌
{
公共令牌类型令牌类型;
公共字符串值;
}
私有字符串语句;
私有int游标=0;
私人代币屈膝;
私有对象目标;
公共计算器(字符串语句、对象目标)
{
这个。声明=声明;
}
公共布尔评估遗嘱()
{
GetNextToken();
bool value=EvaluateExpression();
if(curToken!=null&&curToken.TokenType==TokenType.Operator)
{
var op=curToken;
GetNextToken();
var v2=EvaluateExpression();
如果(op.Value==“和”)
返回值&&v2;
其他的
返回值| | v2;
}
返回值;
}
私有bool EvaluateExpression()
{
if(curToken.TokenType==TokenType.圆括号)
{
GetNextToken();
bool value=EvaluateExpression();
GetNextToken();//跳过右括号
返回值;
}
变量fieldName=curToken.Value;
GetNextToken();
var op=缩减值;
GetNextToken();
var targetValue=缩减值;
GetNextToken();
var fieldValue=GetFieldValue(目标,字段名);
返回EvaluateComparer(fieldValue,targetValue,op);
}
私有布尔求值比较器(字符串左、字符串右、字符串运算)
{
如果(op=“=”)
{
返回左==右;
}
否则如果(op==“!=”)
{
返回左侧!=右侧;
}
//在此处添加更多操作
其他的
{
抛出新异常(“无效操作”);
}
}
/// 
///从输入字符串中获取下一个标记,将其放入“curToken”并前进“cursor”。
/// 
public void GetNextToken()
{
//跳过空格
while(cursor=语句长度)
{
curToken=null;
}
var余数=语句.子字符串(游标);
if(余数。以“名称”开头)
{
光标+=“名称”。长度;
curToken=新令牌
{
TokenType=TokenType.Field,
Value=“Name”
};
}
else if(余数。开始使用(“!=”)
{
光标+=“!=”。长度;
curToken=新令牌
{
TokenType=TokenType.Field,
Value=“!=”
};
}
//等等。
其他的
{
抛出新异常(“意外令牌”);
}
}
私有字符串GetFieldValue(对象目标,字符串字段名)
{
//使用一组固定的字段名实现非常简单
抛出新的NotImplementedException();
}
}

这是您在中的操作方法

扫描

"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值作为求值。

递归下降解析器()可以轻松展开,以便在解析时对表达式求值生成代码有什么问题?您可以直接执行生成的代码,这比临时解释容易得多。谢谢,将尝试实现Tomorrow谢谢,将尝试实现Tomorrow此解决方案非常优雅,但是另一个与我的项目集成得更好。谢谢你的回答!
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 };