我正在开发一个项目,用户可以使用特殊格式输入事实和规则但我在检查格式是否正确并获取信息方面遇到了一些麻烦。
当程序启动时,用户可以输入"命令"进入textarea并将此文本发送到parseCommand
方法,该方法根据用户编写的内容确定要执行的操作。例如,要添加事实或规则,您可以使用前缀+
。或使用-
删除事实或规则等。
我已经创建了处理前缀的系统,但我对事实和规则格式有疑问。
事实:这些由字母数字名称定义,并包含属性列表(每个属性都带有<>
个符号)和真值。属性也由字母数字名称定义,并包含2个字符串(称为参数),每个字符串都带有<>
个符号。通过在列表中放置!
之前,属性也可以为负数。例如,用户可以键入以下内容将这3个事实添加到程序中:
+father(<parent(<John>,<Jake>)>, true)
+father(<parent(<Jammie>,<Jake>)>, false)
+father(!<parent(<Jammie>,<Jake>)>, true)
+familyTree(<parent(<John>,<Jake>)>, <parent(<Jammie>,<Jake>)> , true)
+fathers(<parent(<John>,<Jake>)>, !<parent(<Jammie>,<Jake>)> , true)
我用来存储事实的类是这样的:
public class Fact implements Serializable{
private boolean truth;
private ArrayList<Property> properties;
private String name;
public Fact(boolean truth, ArrayList<Property> properties, String name){
this.truth = truth;
this.properties = properties;
this.name = name;
}
//getters and setters here...
}
规则:这些是2个属性之间的链接,它们由=>
符号标识。同样,它们的名称是字母数字。属性是有限的,因为它们只能有由大写字母组成的参数,第二个属性的参数必须与第一个属性的参数相同。规则还有2个其他参数,可以通过输入名称来设置或不设置(这些参数中的每一个都对应于规则的属性,可以是Negative
或Reversive
)。例如:
+son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>)
+son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>, Negative, Reversive)
+son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>, Reversive)
+son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>, Negative)
规则属性
正常规则告诉我们,如果在下面的示例中,X
是Y
的父级,则表示Y
是X
的孩子:
son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>)
虽然Negative
规则告诉我们,如果在下面的示例中,X
是Y
的父级,则这意味着Y
不是X
的子级1}}:
son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>, Negtive)
但Reversive
规则告诉我们,如果在下面的示例中,Y
是X
的孩子,则这意味着X
是{{1}的父级}}
Y
最后一种情况是规则同时为son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>, Reversive)
和Negative
。这告诉我们,如果在下面的示例中,Reversive
不是Y
的孩子,则表示X
是X
的父级。
Y
这是我用来存储规则的类:
son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>, Negative, Reversive)
物业类:
public class Rule implements Serializable{
private Property derivative;
private Property impliant;
private boolean negative;
private boolean reversive;
private String name;
public Rule(Property derivative, Property impliant, boolean negative, boolean reversive) throws InvalidPropertyException{
if(!this.validRuleProperty(derivative) || !this.validRuleProperty(impliant))
throw new InvalidPropertyException("One or more properties are invalid");
this.derivative = derivative;
this.impliant = impliant;
this.negative = negative;
this.reversive = reversive;
}
//getters and setters here
}
以上示例均为有效输入。这里只是澄清一些无效的输入示例:
事实:
没有为参数提供真或假:
public class Property implements Serializable{
private String name;
private String firstArgument;
private String secondArgument;
public Property(String name, String firstArgument, String secondArgument){
this.name = name;
this.firstArgument = firstArgument;
this.secondArgument = secondArgument;
}
没有给出任何财产:
+father(<parent(<John>,<Jake>)>)
提供了无效的属性:
+father(false)
请注意最后一个中缺少的括号。
规则:
一个或多个属性无效:
+father(<parent(<John>)>, true)
+father(<parent(John, Jake)>, true)
+father(<parent(John, Jake, Michel)>, true)
+father(parent(<John>,<Jake>), true)
我能够从用户那里获得输入,并且我还能够根据前缀查看用户想要执行哪种操作。
但是,我无法弄清楚如何处理字符串,如:
+son(<parent(<X>,<Y>)> => child(<Y>,<X>))
+son(parent(<X>,<Y>) => child(<Y>,<X>))
+son(<parent(<X>,<Y>)> => <child(<Z>,<X>)>) (Note the Z in the child property)
+son(<parent(<Not Valid>,<Y>)> => child(<Y>,<X>)) (Invalid argument for first property)
+son(=> child(<Y>,<X>))
这是由于多种原因:
+familyTree(<parent(<John>,<Jake>)>, <parent(<Jammie>,<Jake>)> , true)
和()
符号拆分输入字符串。<>
属性的位置。Negative
来设置此事实的属性,我可以检查+familyTree(<parent(<John>,<Jake>)>,
之间可能形成问题的任何内容,因为那里在第一个<>
<
醇>
我的第一个想法是从字符串的开头开始(我从前缀中获取操作),然后从主字符串中删除该字符串。
但是,我不知道如何使这个系统适应上述问题(特别是问题1和2)。
我尝试使用以下功能:>
和String.split()
。
我该怎么做呢?如何理解并非所有字符串都包含相同的信息? (从某种意义上说,某些事实具有更多属性或某些规则具有比其他规则更多的属性。)
编辑:
我忘了说用于存储数据的所有方法都已完成并正常工作,可以通过调用例如:String.contains()
或infoHandler.addRule()
来使用它们。在这些功能中,我还可以验证输入数据是否更好。
编辑2:
在评论中有人建议使用像ANTLR或JavaCC这样的解析器生成器。我在过去3天内查看了该选项,但我似乎无法找到有关如何在其中定义自定义语言的任何好消息来源。大多数文档假设您正在尝试编译令人兴奋的语言,并建议从某个地方下载语言文件而不是自己编写。
我试图了解ANTLR的基础知识(这似乎是最容易使用的基础知识。)然而,网上没有很多资源来帮助我。
如果这是一个可行的选择,有人可以帮我理解如何在ANTLR中做这样的事情吗?
一旦我写了一个语法文件,我该如何使用它呢?我已经阅读了一些关于从语言文件生成解析器的内容,但我似乎无法弄清楚这是如何完成的......
编辑3:
我已经开始研究ANTLR的语法文件,如下所示:
infoHandler.removeFact()
我在这里走在正确的轨道上吗?如果这是一个好的语法文件,我怎样才能在以后测试和使用它?
答案 0 :(得分:3)
我担心我无法从你的描述中找出你想要解析的确切语法,但我知道你正试图从解析的语法中创建实体对象。以下几个演示文件演示了如何使用ANTLR-4和Maven:
的pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.stackoverflow</groupId>
<artifactId>communicate</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven-compiler.version>3.6.1</maven-compiler.version>
<java.version>1.8</java.version>
<antlr.version>4.5.3</antlr.version>
<commons-io.version>2.5</commons-io.version>
<junit.version>4.12</junit.version>
</properties>
<build>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<targetPath>com/stackoverflow/test/communicate/resources</targetPath>
</testResource>
</testResources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.antlr</groupId>
<artifactId>antlr4-maven-plugin</artifactId>
<version>${antlr.version}</version>
<configuration>
<sourceDirectory>${basedir}/src/main/resources</sourceDirectory>
<outputDirectory>${basedir}/src/main/java/com/stackoverflow/communicate/frontend</outputDirectory>
</configuration>
<executions>
<execution>
<goals>
<goal>antlr4</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
<version>${antlr.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
的src /主/资源/ communicate.g4
grammar communicate;
@header {
package com.stackoverflow.communicate.frontend;
}
fact returns [com.stackoverflow.communicate.ir.Property value]
: property { $value = $property.value; }
;
property returns [com.stackoverflow.communicate.ir.Property value]
: STRING '(<' argument { com.stackoverflow.communicate.ir.ArgumentTerm lhs = $argument.value; } '>,<' argument '>)' { $value = new com.stackoverflow.communicate.ir.Property($STRING.text, lhs, $argument.value); }
;
argument returns [com.stackoverflow.communicate.ir.ArgumentTerm value]
: STRING { $value = new com.stackoverflow.communicate.ir.ArgumentTerm($STRING.text); }
;
STRING
: [a-zA-Z]+
;
的src /主/ JAVA / COM /计算器/通信/ IR / ArgumentTerm.java
package com.stackoverflow.communicate.ir;
public class ArgumentTerm {
public String Value;
public ArgumentTerm(String value) {
Value=value;
}
}
的src /主/ JAVA / COM /计算器/通信/ IR / Property.java
package com.stackoverflow.communicate.ir;
public class Property {
public String Name;
public ArgumentTerm Lhs;
public ArgumentTerm Rhs;
public Property(String name, ArgumentTerm lhs, ArgumentTerm rhs) {
Name=name;
Lhs=lhs;
Rhs=rhs;
}
}
的src /测试/资源/前端/ father.txt
parent(<John>,<Jane>)
的src /测试/ JAVA / COM /计算器/测试/通信/前端/ FrontendTest.java
package com.stackoverflow.test.communicate.frontend;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.antlr.v4.runtime.ANTLRFileStream;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.junit.Assert;
import org.junit.Test;
import com.stackoverflow.communicate.frontend.communicateLexer;
import com.stackoverflow.communicate.frontend.communicateParser;
public class FrontendTest {
private String testResource(String path) throws IOException {
File file=null;
try {
file=File.createTempFile("test", ".txt");
try(InputStream is=new BufferedInputStream(
FrontendTest.class.getResource(path).openStream());
OutputStream fos=new FileOutputStream(file);
OutputStream os=new BufferedOutputStream(fos)) {
IOUtils.copy(is, os);
}
CharStream fileStream=new ANTLRFileStream(file.getAbsolutePath());
communicateLexer lexer=new communicateLexer(fileStream);
TokenStream tokenStream=new CommonTokenStream(lexer);
communicateParser parser=new communicateParser(tokenStream);
ParseTree tree=parser.fact();
return tree.toStringTree(parser);
} finally {
FileUtils.deleteQuietly(file);
}
}
@Test
public void testArgumentTerm() throws IOException {
Assert.assertEquals(
"(fact (property parent (< (argument John) >,< (argument Jane) >)))",
testResource(
"/com/stackoverflow/test/communicate/resources/frontend/father.txt"));
}
}
如果您调用 mvn antlr4:antlr4 ,附加的POM文件将为语法 communic.g4 生成解析器类( communicParser )。 FrontendTest 是一个JUnit单元测试,用于解析 father.txt 的内容,该内容创建一个名为&#34; parent&#的 Property 实体。 34;并包含两个参数术语对象 John 和 Jane 。
在此处上传包含这些文件的完整Eclipse Java项目:https://www.file-upload.net/download-13056434/communicate.zip.html
答案 1 :(得分:1)
我认为语法分析器不适合您的问题。无论如何,你可以通过使用正则表达式和一些字符串实用程序来更简单地处理它。
最好从小问题开始,然后转向更大的问题: 首先解析属性本身似乎很容易,所以我们写一个方法来做到这一点:
private static Property toProp(String propStr) {
String name = propStr.substring(1,propStr.indexOf("("));
String[] arguments = propStr.substring(propStr.indexOf('(')+1,propStr.indexOf(')')).split(",");
return new Property(name,
arguments[0].substring(1,arguments[0].length()-1),
arguments[1].substring(1,arguments[1].length()-1));
}
要解析Fact字符串,使用正则表达式使事情变得更容易,属性的正则表达式是 /&lt; [\ w \ d] ([&lt;&gt; \ w \ d,] )&gt; / 并通过帮助我们已经编写了toProp方法,我们可以创建另一个解析Facts的方法:
public static Fact handleFact(String factStr) {
Pattern propertyPattern = Pattern.compile("<[\\w\\d]*\\([<>\\w\\d,]*\\)>");
int s = factStr.indexOf("(") + 1;
int l = factStr.lastIndexOf(")");
String name = factStr.substring(0,s-1);
String params = factStr.substring(s, l);
Matcher matcher = propertyPattern.matcher(params);
List<Property> props = new ArrayList<>();
while(matcher.find()){
String propStr = matcher.group();
props.add(toProp(propStr));
}
String[] split = propertyPattern.split(params);
boolean truth = Boolean.valueOf(split[split.length-1].replaceAll(",","").trim());
return new Fact(truth,props,name);
}
解析规则与事实非常相似:
private static Rule handleRule(String ruleStr) {
Pattern propertyPattern = Pattern.compile("<[\\w\\d]*\\([<>\\w\\d,]*\\)>");
String name = ruleStr.substring(0,ruleStr.indexOf('('));
String params = ruleStr.substring(ruleStr.indexOf('(') + 1, ruleStr.lastIndexOf(')'));
Matcher matcher = propertyPattern.matcher(params);
if(!matcher.find())
throw new IllegalArgumentException();
Property prop1 = toProp(matcher.group());
if(!matcher.find())
throw new IllegalArgumentException();
Property prop2 = toProp(matcher.group());
params = params.replaceAll("<[\\w\\d]*\\([<>\\w\\d,]*\\)>","").toLowerCase();
return new Rule(name,prop1,prop2,params.contains("negative"),params.contains("reversive"));
}