[英]java complex logical conditions parser
我有一組傳入記錄,需要在一組定義和存儲的邏輯子句下進行評估。 一個示例邏輯子句如下:
Acct1 != 'Y' AND Acct2 > 1004 AND Acct3 >= 96 AND Acct4 < 1004 AND Acct5 = 99 AND ((Acct6 <= 9090 OR Acct7 IN (A1,A2,A6) AND Acct1 NOT IN (A3,A4)) AND Formatted LIKE 'LINUX' AND Acct9 NOT LIKE 'WINDOWS' AND (Acct10 = 'N' AND NOT Acct11 = 'N') AND EditableField BETWEEN (10 AND 20) )
我輸入到該條款的數據如下:
map.put(Acct1,"Y")
map.put(Acct2,1010)
map.put(Acct3,99)
map.put(Acct4,1015)
map.put(Acct5,99)
map.put(Acct6,9090)
map.put(Acct7,"A3")
map.put(Formatted,"LINUX_INST")
map.put(Updated,"LINUX_TMP")
map.put(Acct10,"Y")
map.put(Acct11,"N")
map.put(EditableFIeld,25)
我必須將填充到映射中的傳入記錄評估到上面定義的子句上,並根據評估結果打印 true 或 false。
子句條件和映射值也將被更改和執行。
我有以下條件子句需要評估:
!=
>
>=
<
=
<=
IN(
NOT IN(
LIKE(
NOT LIKE(
BETWEEN(
AND
OR
AND NOT
OR NOT
我曾嘗試使用語法生成器,但我被告知它不是我們應用程序的推薦解決方案,因此我正在尋找 Java 代碼,並且我有這個詳細的示例供參考 AND,OR,=。 解析邏輯操作 - AND、OR、動態循環條件並尋找片段以在可能的情況下構建在其之上。
如果您想避免使用解析器生成器,請考慮使用 StreamTokenizer 來實現遞歸下降解析器,每個語法規則使用一個方法。
對於您的語法的子集,這應該大致如下(並且應該可以直接擴展到您的完整語法):
public class Parser {
public static Node parse(String expr) {
StreamTokenizer tokenizer =
new StreamTokenizer(new StringReader(expr));
tokenizer.nextToken();
Parser parser = new Parser(tokenizer);
Node result = parser.parseExpression();
if (tokenizer.ttype != StreamTokenizer.TT_EOF) {
throw new RuntimeException("EOF expected, got "
+ tokenizer.ttype + "/" + tokenizer.sval);
}
private StreamTokenizer tokenizer;
private Parser(StreamTokenizer tokenizer) {
this.tokenizer = tokenizer;
}
private Node parseExpression() {
Node left = parseAnd();
if (tokenizer.ttype == StreamTokenizer.TT_WORD
&& tokenizer.sval.equals("OR")) {
tokenizer.nextToken();
return new OperationNode(OperationNode.Type.OR,
left, parseExpression());
}
return left;
}
private Node parseAnd() {
Node left = parseRelational();
if (tokenizer.ttype == StreamTokenizer.TT_WORD
&& tokenizer.sval.equals("AND")) {
tokenizer.nextToken();
return new OperationNode(OperationNode.Type.AND,
left, parseAnd());
}
return left;
}
private Node parseRelational() {
Node left = parsePrimary();
OperationNode.Type type;
switch (tokenizer.ttype) {
case '<': type = OperationNode.Type.LESS; break;
case '=': type = OperationNode.Type.EQUAL; break;
case '>': type = OperationNode.Type.GREATER; break;
default:
return left;
}
tokenizer.nextToken();
return new OperationNode(type, left, parseRelational());
}
private Node parsePrimary() {
Node result;
if (tokenizer.ttype == '(') {
tokenizer.nextToken();
result = parseExpression();
if (tokenizer.ttype != ')') {
throw new RuntimeException(") expected, got "
+ tokenizer.ttype + "/" + tokenizer.sval);
}
} else if (tokenizer.ttype == '"' || tokenizer.ttype == '\'') {
result = new LiteralNode(tokenizer.sval);
} else if (tokenizer.ttype == TT_NUMBER) {
result = new LiteralNode(tokenizer.nval);
} else if (tokenizer.ttype == StreamTokenizer.TT_WORD) {
result = new FieldNode(tokenizer.sval);
} else {
throw new RuntimeException("Unrecognized token: "
+ tokenizer.ttype + "/" + tokenizer.sval);
}
tokenizer.nextToken();
return result;
}
}
這假設有一個像這樣的 Node 對象層次結構:
interface Node {
Object eval(Map<String,Object> data);
}
class FieldNode implements Node {
private String name;
FieldNode(String name) {
this.name = name;
}
public Object eval(Map<String,Object> data) {
return data.get(name);
}
}
class LiteralNode implements Node {
private Object value;
FieldNode(Object value) {
this.value = value;
}
public Object eval(Map<String,Object> data) {
return value;
}
}
class OperationNode implements Node {
enum Type {
AND, OR, LESS, GREATER, EQUALS
}
private Type type;
private Node leftChild;
private Node rightChild;
OperationNode(Type type, Node leftChild, Node rightChild) {
this.type = type;
this.leftChild = leftChild;
this.rightChild = rightChild;
}
public Object eval(Map<String,Object> data) {
Object left = leftChild.eval(data);
Object right = rightChild.eval(data);
switch (type) {
case AND: return ((Boolean) left) && ((Boolean) right);
case OR: return ((Boolean) left) || ((Boolean) right);
case LESS: return ((Comparable) left).compareTo(right) < 0;
case EQUALS: return left.equals(right);
case GREATE: return ((Comparable) left).compareTo(right) > 0;
default:
throw new RuntimeException("Invalid op: " + type);
}
}
為了直接回答這個問題,許多 SO 問題(例如1 , 2 )描述了手動編寫解析器的基礎知識,但實際上,由於樣板和嚴格,在大學編譯器課程之外手動編寫解析器是非常不尋常的涉及的細節。
正如評論中所討論的,聽起來避免語法生成器的主要原因是避免對外部庫的依賴。 但是,當使用像JavaCC (Java Compiler-Compiler) 這樣的語法生成器(解析器生成器)時,不涉及 JAR 文件或外部依賴項:JavaCC 二進制文件將語法規范轉換為 Java 代碼,該代碼可以在不涉及任何其他庫的情況下運行.
請參閱此 IBM 教程,JoAnn Brereton 的“使用 JavaCC 構建用戶友好的布爾查詢語言”(通過 archive.org)作為示例,其中順便涉及一種與您的搜索語言不同的語法。
示例輸入:
actor = "Christopher Reeve" and keyword=action and keyword=adventure
(actor = "Christopher Reeve" and keyword=action) or keyword=romance
actor = "Christopher Reeve" and (keyword=action or keyword=romance)
語法摘錄:
TOKEN :
{
<STRING : (["A"-"Z", "0"-"9"])+ >
<QUOTED_STRING: "\"" (~["\""])+ "\"" >
}
void queryTerm() :
{
}
{
(<TITLE> | <ACTOR> |
<DIRECTOR> | <KEYWORD>)
( <EQUALS> | <NOTEQUAL>)
( <STRING> | <QUOTED_STRING> )
|
<LPAREN> expression() <RPAREN>
}
輸出文件:
這是您可以考慮的幾種解析器生成器之一; 其他人,如yacc 和 bison ,也無需外部庫即可生成獨立的 Java 文件。 如有必要,您可以將生成的 Java 文件直接檢查到您的存儲庫中,僅在需要調整語法時才保留.jj
編譯器源文件。 (盡管最好在構建過程中從源代碼中重新編譯並避免將生成的文件檢查到源代碼管理中,但這可能更適合您對純 Java 解決方案的限制。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.