Вступление
Я работаю над проектом, в котором пользователь может вводить факты и правила в специальном формате, но у меня возникают проблемы с проверкой правильности этого формата и получением информации.
Когда программа запускается, пользователь может вводить "команды" в текстовое поле, и этот текст отправляется методу 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 ;
Я здесь, на правильном пути? Если это хороший файл грамматики, как я могу его проверить и использовать позже?