Вступление
Я работаю над проектом, в котором пользователь может вводить факты и правила в специальном формате, но у меня возникают проблемы с проверкой правильности этого формата и получением информации.
Когда программа запускается, пользователь может вводить "команды" в текстовое поле, и этот текст отправляется методу 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 свойствами, и они идентифицируются знаком =>
Снова их имя является буквенно-цифровым. Свойства ограничены, поскольку они могут иметь только аргументы, состоящие из прописных букв, а аргументы второго свойства должны быть такими же, как и первые. В правилах также есть два других аргумента, которые либо заданы, либо не установлены, введя имя или нет (каждый из этих аргументов соответствует свойству правила, которое может быть 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
:
son(<parent(<X>,<Y>)> => <child(<Y>,<X>)>, Negtive)
Однако правило Reversive
говорит, что если в приведенном ниже примере Y
является дочерним элементом X
это означает, что X
является родителем 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;
}
Вышеприведенные примеры - все допустимые входные данные. Для пояснения здесь приведены некоторые недопустимые примеры ввода:
Факты:
Для аргумента не указано true или false:
+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)
Это связано с рядом причин:
- Количество свойств для факта, введенного пользователем, является переменной, поэтому я не могу просто разбить входную строку на основе знаков
()
и<>
. - Для правил иногда последние 2 свойства являются переменными, поэтому может случиться так, что свойство "Реверсивные" находится на месте в строке, где вы обычно находите свойство
Negative
. - Если я хочу получить аргументы из этой части входной строки:
+familyTree(<parent(<John>,<Jake>)>,
чтобы установить свойство для этого факта, я могу проверить все, что находится между<>
что может возникнуть проблема, потому что есть 2 открытия<
до первого>
Что я пробовал
Моя первая идея заключалась в том, чтобы начать с начала строки (что я сделал для получения действия из префикса), а затем удалить эту часть строки из основной строки.
Однако я не знаю, как адаптировать эту систему к проблемам выше (особенно проблема № 1 и 2).
Я пытался использовать такие функции, как: String.split()
и String.contains()
.
Как мне это сделать? Как я могу понять, что не все строки содержат одну и ту же информацию? (В некотором смысле, что некоторые факты имеют больше свойств или некоторые правила имеют больше атрибутов, чем другие).
РЕДАКТИРОВАТЬ:
Я забыл сказать, что все методы, используемые для хранения данных, закончены и работают, и их можно использовать, например, путем вызова: infoHandler.addRule()
или infoHandler.removeFact()
. Внутри этих функций я мог бы также проверить входные данные, если это лучше.
Я мог бы, например, просто получить все данные факта или правила из строки и проверить такие вещи, как аргументы свойств правил, только с использованием прописных букв и т.д.
EDIT 2:
В комментариях кто-то предложил использовать генератор парсера, такой как ANTLR или JavaCC. Я рассмотрел этот вариант за последние 3 дня, но я не могу найти хороший источник того, как определить в нем пользовательский язык. В большинстве документов предполагается, что вы пытаетесь скомпилировать захватывающий язык и рекомендуем загружать языковой файл откуда-то, а не писать свои собственные.
Я пытаюсь понять основы ANTLR (который, кажется, самый простой в использовании.) Однако в Интернете не так много ресурсов, чтобы помочь мне.
Если это жизнеспособный вариант, может ли кто-нибудь помочь мне понять, как сделать что-то подобное в ANTLR?
Кроме того, как только я написал файл с грамматикой, как я могу его использовать? Я читал что-то о создании парсера из языкового файла, но я не могу понять, как это делается...
ИЗМЕНИТЬ 3:
Я начал работать над грамматическим файлом для ANTLR, который выглядит так:
/** Grammer used by communicate parser */
grammar communicate;
/*
* Parser Rules
*/
argument : '<' + NAMESTRING + '>' ;
ruleArgument : '<' + RULESTRING + '>' ;
property : NAMESTRING + '(' + argument + ',' + argument + ')' ;
propertyArgument : (NEGATIVITY | POSITIVITY) + property + '>' ;
propertyList : (propertyArgument + ',')+ ;
fact : NAMESTRING + '(' + propertyList + ':' + (TRUE | FALSE) + ')';
rule : NAMESTRING + '(' + ruleArgument + '=>' + ruleArgument + ':' + RULEOPTIONS + ')' ;
/*
* Lexer Rules
*/
fragment LOWERCASE : [a-z] ;
fragment UPPERCASE : [A-Z] ;
NAMESTRING : (LOWERCASE | UPPERCASE)+ ;
RULESTRING : (UPPERCASE)+ ;
TRUE : 'True';
FALSE : 'False';
POSITIVITY : '!<';
NEGATIVITY : '<' ;
NEWLINE : ('\r'? '\n' | '\r')+ ;
RULEOPTIONS : ('Negative' | 'Negative' + ',' + 'Reversive' | 'Reversive' );
WHITESPACE : ' ' -> skip ;
Я здесь, на правильном пути? Если это хороший файл грамматики, как я могу его проверить и использовать позже?