У меня есть определенные логические предложения, и я хочу проверить их правильность с помощью Regex в C #.
Каждая заглавная буква является предикатом. Формулы для логики предикатов построены с предикатами и связями, такими как ¬, ⇒, ⇔, ⋀ и ⋁. Однако пользовательский ввод должен быть в строковой нотации ASCII, а именно:
Logical notation ASCII
¬A ~(A) Negation
A ⇒ B >(A,B) Implication
A ⇔ B =(A,B) Bi-Implication
A ⋀ B &(A,B) AND
A ⋁ B |(A,B) OR
Кроме того, True и False представлены 0 и 1, например, так: & (0,1)
Допустим, у меня есть следующий вход ASCII
string input1 = "&(&(=(A,B),>(&(A,B),~(C))),>(A,~(&(A,B))))"; // valid
string input2 = "1" // valid
string input3 = "=(~(A),>(>(B,C),~(A)))" // valid
string input4 = "(~(A))" // invalid because no connective in the beginning
string input5 = ">(A,B" // invalid because no closing parenthesis
Таким образом, строка ascii должна быть
- Единственный Предикат - A-Z или 0-1
- строка, начинающаяся с соединительного и содержащая два предложения, разделенных запятой, эти предложения могут быть либо одним предикатом, либо связующим с двумя предложениями ...
Я придумал это:
Regex checkExpression = new Regex(
@"([&|>=]\(([A-Z0-1]{1}|\(.*\)),([A-Z0-1]{1}|\(.*\))\))
|([~]\(([A-Z0-1]{1}|\(.*\))\))");
Тем не менее, я не очень знаком со сборкой регулярных выражений, любая помощь приветствуется.
5 ответов
Я создал регулярное выражение, которое может сделать это. Регулярное выражение: ^([01A-Z](?![01A-Z])|(?<dyadic>[|&>=]\()|(?<comma-dyadic>\,)|(?<dBracket-comma>\))|(?<unary>~\()|(?<uBracket-unary>\)))+(?(dyadic)(?!))(?(comma)(?!))(?(unary)(?!))$
Это намного чище и лучше в PCRE, потому что вы можете делать рекурсию ^([A-Z01])|([>=&|])\((?R),(?R)\)|~\((?R)\)$
, но это недоступно в C # в стиле regex.
Мне пришлось изучить балансировочную группу из C #, поэтому вам, возможно, придется изучить это.
Разбивка того, как работает код:
^ # Start of line
( # Either
[01A-Z](?![01A-Z])| # A symbol or bool followed by anything else
(?<dyadic>[|&>=]\((?!,))| # Start of dyadic
(?<comma-dyadic>,(?!\)))| # Looks for comma followed by dyadic. Pops off the dyadic stack.
(?<dBracket-comma>\))| # Looks for ending bracket following comma. pops off comma stack.
(?<monadic>~\((?!\)))| # Start of monadic function.
(?<uBracket-monadic>\))) # Looks for ending bracket for unary. Pops off the monadic stack.
+ # Any number of times.
(?(dyadic)(?!)) # Assert dyadic stack is empty. All have a comma.
(?(comma)(?!)) # Assert comma stack is empty. All dyadic commas followed by brackets.
(?(monadic)(?!)) # Assert monadic stack is empty. All monadic expressions have closing brackets.
$ # End of line.
Пример демо-версии.
Обновление: забыл убедиться, что в каждой функции есть параметр. Отрицательный прогноз был добавлен в 3 местах, чтобы исправить это.
Update2: Сделано, чтобы регулярное выражение совпадало только с однобуквенными литералами. Добавлен негативный взгляд, который проверяет, сопровождается ли буквой или цифрой буквой или цифрой.
Как уже было сказано, регулярное выражение не является хорошим инструментом для этого. Даже если бы вы могли это сделать (я скорее сомневаюсь, что в этом случае), часто вам нужно не только подтвердить это, но и оценить. И вы действительно не хотите делать это с помощью регулярных выражений. И хотя для подхода к регулярным выражениям это кошмар, для грамматического парсера это просто пирог. Если вам нужно что-то более легкое, для простых случаев вы даже можете написать все самостоятельно, анализаторы LL (1) имеют достаточно описательный код:
public class ParseException : Exception
{
public ParseException(string message) : base(message) { }
}
public class Analyzer
{
protected int position;
protected string input;
protected Dictionary<char, bool> predicates;
public Analyzer(string input)
{
this.input = input;
}
public bool? Evaluate(Dictionary<char, bool> predicates = null)
{
position = 0;
this.predicates = predicates;
try
{
bool value = T();
if (position == input.Length)
{
return value;
}
}
catch (ParseException)
{
}
return null;
}
protected char GetChar()
{
if (position >= input.Length)
{
throw new ParseException("Unexpected end of input");
}
return input[position++];
}
protected void MatchChar(char c)
{
if (GetChar() != c)
{
throw new ParseException("Invalid input");
}
}
protected bool T()
{
char c = GetChar();
if (c == '~')
{
MatchChar('(');
bool val = T();
MatchChar(')');
return !val;
}
if (c == '>')
{
MatchChar('(');
bool val1 = T();
MatchChar(',');
bool val2 = T();
MatchChar(')');
return val2 || !val1;
}
if (c == '=')
{
MatchChar('(');
bool val1 = T();
MatchChar(',');
bool val2 = T();
MatchChar(')');
return val1 == val2;
}
if (c == '&')
{
MatchChar('(');
bool val1 = T();
MatchChar(',');
bool val2 = T();
MatchChar(')');
return val1 && val2;
}
if (c == '|')
{
MatchChar('(');
bool val1 = T();
MatchChar(',');
bool val2 = T();
MatchChar(')');
return val1 || val2;
}
if (c == '0')
{
return false;
}
if (c == '1')
{
return true;
}
if (c >= 'A' && c <= 'Z')
{
if (predicates == null) { return false; }
if (predicates.TryGetValue(c, out bool val))
{
return val;
}
throw new ParseException("Predicate value not found");
}
throw new ParseException("Invalid input");
}
}
Вы можете проверить действительность следующим образом:
bool ok1 = new Analyzer(input1).Evaluate().HasValue;
И оцените так:
var values1 = new Dictionary<char, bool>() { ['A'] = true, ['B'] = false, ['C'] = true };
bool result1 = new Analyzer(input1).Evaluate(values1).Value;
Если я вас правильно понимаю, вы хотите проверить синтаксис данного предложения.
Это можно легко сделать путем зацикливания и сжатия каждой действительной формулы для одного предиката, скажем, 1
. Повторение этого до тех пор, пока остаток не станет единственным 1
, будет сигнализировать правильное предложение. Завершение с No Match сигнализирует о неверном предложении.
Иллюстрация:
&(&(=(A,B),>(&(A,B),~(C))),>(A,~(&(A,B)))) Proposition
&(&(1,>(1,1)),>(A,~(1))) First iteration
&(&(1,1),>(A,1)) Second iteration
&(1,1) Third iteration
1 Fourth iteration
=(~(A),>(>(B,C),~(A))) Proposition
=(1,>(1,1)) First iteration
=(1,1) Second iteration
1 Third iteration
(~(A)) Proposition
(1) First iteration
No Match
>(A,B Proposition
No Match
Ваше регулярное выражение работает, но я немного упростил его:
~\([A-Z0-1]\)|[&|>=]\([A-Z0-1],[A-Z0-1]\)
Вот живая демонстрация в ideone.
Использование регулярных выражений для синтаксического анализа языка возможно, но очень быстро становится очень сложным.
Я предлагаю использовать Абстрактные деревья синтаксиса (AST). Мне нравится ANTLR. Хорошее вступление можно найти по адресу ANTLR с C # - с использованием абстрактного синтаксического дерева (AST)
Как сказал Ричард, вы должны использовать AST для управления валидацией, и на самом деле вы также можете использовать это, чтобы начать создавать свой собственный язык на C #. Я делал это много раз в прошлом для различных проектов и использовал довольно приличный инструмент под названием "Irony.Net" ирония, где вы непосредственно разрабатываете грамматику в коде.
Irony - это набор разработчика для реализации языков в .NET Платформа. В отличие от большинства существующих решений в стиле yacc / lex, Irony не использовать любой сканер или генерацию кода парсера из грамматики спецификации написаны на специализированном метаязыке. В иронии грамматика целевого языка кодируется непосредственно в C # с помощью оператора перегрузка для выражения грамматических конструкций. Сканер и парсер Иронии модули используют грамматику, закодированную как класс c # для управления синтаксическим анализом обработать. Irony.Net CodePlex
С этим придумали довольно основную грамматику, которая, кажется, обрабатывает ваши случаи ниже. Однако в ваших примерах есть странный случай (или требует дальнейшего объяснения)
1
действителен, но является ли0
действительным?- Как и выше, как насчет любого из предикатов
A-Z
(верхний регистр)?
Пример грамматики
[Language("Logical Proposition", "1.0", "")]
public class LogicalPropositionGrammar : Grammar
{
public LogicalPropositionGrammar()
{
//syntax terminals
var lpar = ToTerm("(");
var rpar = ToTerm(")");
var comma = ToTerm(",");
var trueTerm = ToTerm("1") | "true";
var falseTerm = ToTerm("0") | "false";
//nonterms
var predicate = new NonTerminal("Predicate");
var connective = new NonTerminal("Connective");
var pexp = new NonTerminal("PredExpression");
var formula = new NonTerminal("Formula");
var literal = new NonTerminal("Literal");
var singleTerm = new NonTerminal("SingleTerm");
var multiTerm = new NonTerminal("MultiTerm");
//formulat non terms
var negation = new NonTerminal("Negation");
var implication = new NonTerminal("Implication");
var biImplication = new NonTerminal("Bi-Implication");
var andTerm = new NonTerminal("And");
var orTerm = new NonTerminal("Or");
literal.Rule = trueTerm | falseTerm;
singleTerm.Rule = lpar + pexp + rpar; //single term is (pexp)
multiTerm.Rule = lpar + pexp + comma + pexp + rpar; //mult term = (pexp, pexp)
//formula rules
negation.Rule = ToTerm("~") + singleTerm;
implication.Rule = ToTerm(">") + multiTerm;
biImplication.Rule = ToTerm("=") + multiTerm;
andTerm.Rule = ToTerm("&") + multiTerm;
orTerm.Rule = ToTerm("|") + multiTerm;
//predicate terms
predicate.Rule = ToTerm("A") | "B" | "C" | "D" | "E" | "F" | "G" |
"H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" |
"P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" |
"X" | "Y" | "Z" | literal;
//predicate rule
pexp.Rule = predicate | negation | implication | biImplication | andTerm | orTerm;
//a base formulat
formula.Rule = MakeStarRule(formula, pexp);
RegisterOperators(10, "&", "~", ">", "=", "|");
MarkPunctuation(",", "(", ")");
MarkTransient(pexp, singleTerm);
Root = formula;
}
}
Похожие вопросы
Новые вопросы
c#
C # (произносится как «резкий») - это высокоуровневый, статически типизированный язык программирования с несколькими парадигмами, разработанный Microsoft. Код C # обычно нацелен на семейство инструментов и сред выполнения Microsoft .NET, включая, среди прочего, .NET Framework, .NET Core и Xamarin. Используйте этот тег для вопросов о коде, написанном на C # или в формальной спецификации C #.