Подтвердить что ты не робот

Очень простой английский грамматический анализатор

Я пишу очень простой синтаксический анализатор (в основном, чтобы лучше понять, как они работают), который вводит пользовательский ввод нескольких слов, определяет, является ли структура предложения ОК или не совсем, и выводит результат. Грамматика:

Приговор: Существительное глагол

Предложение статьи

Предложение о назначении предложения

Сопряжение: "а также" "или" "А"

Существительное: "птицы" "рыба" "С++"

Глагол: "правила" "Летать" "Плавать"

Статья: "Команда"

Написание грамматики было простым. Он реализует код, который дает мне некоторые проблемы. Мой psuedocode для этого:

main()
get user input (string words;)
while loop (cin >> words)
call sentence()
end main()

sentence()
call noun()
if noun() call verb() (if verb is true return "OK" ???)(else "not ok"???)
else if not noun() call article()
                if article() call sentence() (if sentence is true "OK"???)(else "not"?)
else if not noun() call conjunction()
                   if sentence() conjunction() sentence() - no idea how to implement
                                                             return "OK"
else "not ok"

Итак, мой чрезвычайно неряшливый код psuedo. У меня есть несколько вопросов по его реализации.

  • Для функций слова (существительное, глагол и т.д.), как я должен проверять, являются ли они истинными? (как при проверке, если пользователь вводит птиц, рыбу, летать, плавать и т.д.)

  • Как мне обрабатывать вызов соединения и вывод?

  • Должен ли я обрабатывать выходные данные из основной функции или функций вызова?

  • Ни один из вышеперечисленных вопросов не имеет значения, если мой psuedo-код полностью ошибочен. Что-то не так с основами?

Как добавленное примечание, я нахожусь в главе 6: "Практика и принципы с использованием С++", поэтому я бы предпочел использовать синтаксис языка, который я уже изучил, поэтому все, что относится к категории расширенного программирования вероятно, не очень полезно. (В упражнении конкретно говорится, что не использовать жетоны, поэтому посчитайте их.)

Заранее спасибо

Последнее редактирование: в открытой группе книг я задал тот же вопрос, и Бьярне Страуструп прокомментировал, что он поставил решение для упражнений онлайн. В основном он вводил ввод в функцию предложения и использовал операторы if для возврата true или false. Однако он не использовал статьи, поэтому мой был намного сложнее. Думаю, если бы я научился чему-либо из этого упражнения, то, что, имея дело с большим количеством пользовательских входов, токенизация является ключевой (из того, что я знаю до сих пор.) Вот мой код на данный момент. Я могу вернуться к нему позже, потому что он по-прежнему очень глючит и в основном возвращается только в том случае, если предложение в порядке и не может обрабатывать такие вещи, как (существительное, соединение, предложение), но пока я продвигаюсь.

#include "std_lib_facilities.h"

bool article(string words)
{
               if (words == "the")
               return true;
               else return false;        
}

bool verb(string words)
{
               if (words == "rules" || words == "fly" || words == "swim")
               return true;
               else return false;                   
}

bool noun(string words)
{
               if (words == "birds" || words == "fish" || words == "c++")
               return true;
               else return false;                   
}

bool conjunction(string words)
{
              if (words == "and" || words == "but" || words == "or")
              return true;
              else return false;                  
}

bool sentence()
{
string w1;
string w2;
string w3;
string w4;

cin >> w1;
if (!noun(w1) && !article(w1)) return false; // grammar of IFS!

cin >> w2;
if (noun(w1) && !verb(w2)) return false;
if (article(w1) && !noun(w2)) return false;

cin >> w3;
if (noun(w1) && verb(w2) && (w3 == ".")) return true;
if (verb(w2) && !conjunction(w3)) return false;
if (noun(w2) && !verb(w3)) return false;
if (conjunction(w3)) return sentence();

cin >> w4;
if (article(w1) && noun(w2) && verb(w3) && (w4 == ".")) return true;
if (!conjunction(w4)) return false;
if (conjunction(w4)) return sentence();
}


int main()
{                                   
cout << "Enter sentence. Use space then period to end.\n";
            bool test = sentence();
            if (test)
               cout << "OK\n";
            else
               cout << "not OK\n";

keep_window_open();   }

4b9b3361

Ответ 1

Ok. Если вы действительно хотите это сделать вручную: - (

Эта проблема состоит из двух частей:

  • Лексический анализ
  • Синтаксический анализ.
  • Мы можем игнорировать анализ Symantic, так как именно поэтому вверх.

Сначала у вас есть токенизация входного потока в резонансные токены. Слова были бы выбором obvios, но это оставило бы много работы для синтаксической фазы. Поэтому я бы сгруппировал ваши слова в следующие типы (Conjunction, Noun, Verb, Article), а затем написал лексер, чтобы вернуть правильные лексемы.

Lexer.cpp
enum Lexeme { END,Conjunction,Noun,Verb,Article };
Lexem getNextLexme(std::istream in)
{
    std::string word;
    in >> word;

    if (!in) {return END;}

         if (word == "and")   return Conjunction;
    else if (word == "birds") return Noun;
    else if (word == "fly")   return Verb;
    else if (word == "the")   return Article;

    ... etc
}

Итак, теперь вы можете получить синтаксический синтаксический анализатор в терминах упрощенного потока токенов.

bool ParseSentence(std::istream in)
{
    Lexeme token = getNextLexme(in);
    switch(token)
    {
        case Noun:    if (!parseVerb(in))
                      {    return false;
                      }
                      return parseConjunctionOrEnd(in);
        case Article: return ParseSentence();
        case END:     return true;
    }
}
bool parseVerb(std::istream in)
{
    Lexeme token = getNextLexeme(in);
    if (token != Verb) { /*ERROR*/ return false;}
    return true;
 }
 // etc

Другим вариантом синтаксического анализа является построение таблицы состояний. Но это требует ручного анализа грамматики и определения состояний. Это следует использовать только с самыми тривиальными грамматистами, все, что вам нужно, должно быть оставлено на инструментах, которые могут автоматически генерировать таблицу состояний.

Итак, предположим, что грамматик, который я определил в своем первоначальном сообщении ниже:
И надеюсь, что я исправлю это, потому что я не раздувающийся инструмент: -)

State 1:   Start <Nothing Happened>
               Article -> State 2
               Noun -> State 3
               Otherwise Error
State 2:   Seen Article.
               Noun -> State 3
               Otherwise Error
State 3:   Seen Noun  in Sentence.
               Verb -> State 4
               Otherwise Error
State 4:   Seen Noun Verb
               End -> State 5
               Conjunction -> State 1
State 5:   Finished:

State 0:   Error State.


int stateTable[][]    // CurrentState,CurrentObject
           = {/*State 0: Error State:*/{},
                                       // END,Conjunction,Noun,Verb,Article 
              /*State 1: Start*/       {  0,  0,          3,   0,   2},
              /*State 2: Article*/     {  0,  0,          3,   0,   0},
              /*State 3: Noun*/        {  0,  0,          0,   4,   0},
              /*State 4: Noun Verb*/   {  5,  1,          0,   0,   0},
              /*State 5: End*/         {}
             };

bool parseSentence(std::iostream& in)
{
    int currentState = 1;
    while((currentState != 0) && (currentState != 5))
    {
        int token = getNextLexme(in);
        currentState = stateTable[currentState][token];
    }
    return currentState == 5;
}

Ответ 2

Я заинтригован этим вопросом. Я собираюсь помочь OP, Алекс, что-нибудь приготовить, но так как я не очень хорошо знаю С++, это будет в псевдо-С++. Он не будет использовать lex/yacc, потому что Алекс хочет узнать, как они сделаны. Инструменты, такие как lex и yacc, становятся "черными ящиками", если вы их используете. У меня нет времени собрать все это прямо сейчас, но я могу работать над ним по частям в течение нескольких часов. Я просто хотел, чтобы это началось сейчас.

Первое, что нам нужно сделать, это очистить грамматику. У вас есть предложение, определенное следующим образом:

sentence : noun verb
         | article sentence
         | sentence conjunction sentence

Эта грамматика ошибочна. Предложение, такое как "плавание рыбы", справедливо, поскольку предложение определяется в терминах самого себя. Рекурсия является нормальной частью грамматик, но ее нужно обрабатывать правильно. Я собираюсь помешать предположить, что вы не хотите, чтобы в строке появлялись две или более статьи.

Лучшей грамматикой для предложения может быть:

sentence : clause conjunction clause
         | clause

clause : nounphrase verbphrase

nounphrase : noun
           | article noun

Это удаляет неограниченную рекурсию, которая может вызвать бесконечные циклы.

Теперь мы готовы решать сам парсер. Поскольку это С++, мы могли бы также сделать его объектно-ориентированным. Теперь я должен бежать, но я дам вам подсказку: там будет класс для каждого из производственных правил.

Ответ 3

Ладно, так... У меня не будет ответов на ваши конкретные вопросы, но я хочу указать вам на некоторые общие идеи, чтобы думать об этом, работая над этим. Прежде всего, синтаксический анализ сложный. У вас простая грамматика, но это может быть трудно. Компоненты компилятора отвечают за разбор... просто чтобы дать некоторый контекст.

Существует два основных типа синтаксического анализа: синтаксический анализ верхнего и нижнего дескрипторов. Они называются тем, как они проходят дерево синтаксиса (подумайте о том, какое дерево синтаксиса будет создано для возможных конструкций). Поверхность синтаксического анализа проста и, вероятно, будет работать для того, что вы хотите сделать. Наиболее распространенным методом анализа по принципу "сверху вниз" является рекурсивный анализ спуска: http://en.wikipedia.org/wiki/Recursive_descent_parser

Однако, чтобы использовать рекурсивный синтаксический анализ спуска, ваша грамматика должна быть в определенном формате... для некоторых грамматик невозможно рекурсивное схождение разобрать их. Вы должны иметь возможность реформировать свою грамматику, чтобы соответствовать этому, хотя.

Сглаживающие синтаксические анализаторы легко писать... поскольку для небольшого языка вам понадобятся только несколько функций.

Второй способ разбора - синтаксический анализ снизу вверх. Это обычно используется в компиляторах, поскольку у него нет ограничений сверху вниз. Также легче сделать отчет об ошибках, если данная строка не соответствует языку.

Партизаны снизу вверх ТРУДНО писать... большинство людей используют генератор парсера для выполнения работы. Я работал с YACC совсем немного. Вы в основном вводите грамматику (и действия, которые нужно предпринять, когда это правило сопоставляется), и она анализирует грамматик.

Анализаторы Bottom-up используют что-то, называемое shift-reduce parsing. Это метод обработки ввода и согласования правил.

Еще раз взглянув на ваш код, я бы сказал, что вы можете использовать парсер сверху вниз. Извините, я не могу дать конкретный код, но примеры примеров синтаксического анализа сверху вниз (или рекурсивные примеры парсетов спуска), вероятно, вернут код, который вам нужен.

Ответ 4

Большинство парсинга, таких как разбор текста программы, выполняются с помощью формальных грамматических парсеров. Английский и большинство разговорных языков не являются формальными грамматиками, и вам будет очень тяжело разбираться в них. Эта проблема связывает PHD в течение десятилетий без большого успеха.

Ответ 5

Части речи

Чтобы получить части речи, вам понадобится список словарей с частями речи. Помимо хеш-таблицы, переводящей слова в списки частей речи, еще один возможный способ проверить часть (части) речи - загрузить каждый набор слов для каждой части речи в ее собственный Bloom filter (вид сжатой хешированной карты от строк до булевых).

Ответ 6

Одним из аспектов грамматики для естественных языков является то, что они часто неоднозначны. Например, английское предложение:

Я однажды застрелил слона в своей пижаме. Как он попал в мои пижамы, я никогда не узнаю
- Граучо Маркс

Фраза "в моей пижаме" неоднозначно описывает тему "я" или "слон" объекта. Без семантического контекста было бы невозможно правильно построить АСТ.

Если вы хотите этого избежать, вам, вероятно, понадобится какой-то способ рассмотрения неоднозначности полезным способом. Одна из стратегий состоит в том, чтобы произвести все возможные определения неоднозначных фраз. Одним из инструментов, который делает это возможным, является Earley Parser. В отличие от других типов парсеров, таких как рекурсивные партизаны спуска, анализаторы Эрли генерируют все деривации в виде переходов состояния парсера, а не простое дерево. На практике это труднее работать.

Ответ 7

Используйте Flex и Bison:

Граммерное правило в бизоне:

%%

English            :   SentenceList

SentenceList       :   Sentence
                   |   Article  Sentence
                   |   Sentence Conjunction Sentence

Sentence           :   Noun Verb


Conjunction        :   TOKEN_WordAnd
                   |   TOKEN_WordOr
                   |   TOKEN_WordBut


Noun               :   TOKEN_WORD_BIRDS
                   |   TOKEN_WORD_FISH
                   |   TOKEN_WORD_CPP

Verb:              :   TOKEN_WORD_RULES
                   |   TOKEN_WORD_FLY
                   |   TOKEN_WRD_SWIM

Article            :   TOKEN_WORD_THE
%%

Ответ 8

Вы можете взять добычу в Ubiquity, это плагин для Firefox, целью которого является использование естественного языка для выполнения общих веб-задач ( он написан на JavaScript, но, возможно, вы можете получить общий алгоритм, который был бы полезен)

Ответ 9

Прежде чем зайти слишком далеко, написав парсер, могу ли я предложить изучить пару лекс и yacc или flex и bison? Эти инструменты были специально созданы для создания парсеров и лексеров.

Они автоматически сгенерируют ваш код c/С++ (возможно, другой), поэтому вам не придется беспокоиться о всех видах граничных случаев для аргументов пользователя. Вы можете взломать грамматику, которая у вас выше, менее чем за 30 минут.

Что касается ваших вопросов:

Для функций слова (существительное, глагол, и т.д.), как я должен проверять если они верны? (как при проверке, если пользователь вводит птиц, рыбу, муху, плавать и т.д.)

Здесь предлагается щедрое использование strcasecmp(), при этом всевозможные проверки ошибок.

Как я должен обрабатывать соединение вызов и выход?

Я не слишком понимаю, что вы имеете в виду здесь. Я бы просто возвращал какое-то значение дозорного, если оно действительно или нет.

Должен ли я обрабатывать вывод из основная функция или функции вызова?

В основном из функций вызова, поскольку они имеют индивидуальную функциональность, о которой вы беспокоитесь. main() - это просто клей, чтобы держать его вместе.

Ни один из вышеперечисленных вопросов не имеет значения, если мой код psuedo полностью неправильный. Является что-то не так с основами?

Он выглядит так, как у вас есть, но вы избавитесь от огромной головной боли, переключившись на lex/yacc или flex/bison.