我有一个非常简单的域特定语言,包含以下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
答案 0 :(得分:2)
作为练习,我已经快速为您的语言勾画出一个递归下降的解析器/评估器。我省略了最明显的代码(如标记化和字段值检索),我没有注意效率。
在您的实施中,您可能需要考虑
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();
}
}
答案 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值作为评估。