Очень простой синтаксический анализатор английской грамматики - PullRequest
12 голосов
/ 23 июня 2009

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

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

Статья Приговор

Предложение Соединение Предложение

Conjunction: "а также" "или же" "Но"

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

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

Статья: "Символ"

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

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"

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

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

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

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

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

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

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

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

#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 (); }

Ответы [ 9 ]

9 голосов
/ 24 июня 2009

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

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

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

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

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;
}
5 голосов
/ 24 июня 2009

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

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

sentence : noun verb
         | article sentence
         | sentence conjunction sentence

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

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

sentence : clause conjunction clause
         | clause

clause : nounphrase verbphrase

nounphrase : noun
           | article noun

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

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

2 голосов
/ 23 июня 2009

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

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

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

Парсеры сверху вниз легко написать ... так как для небольшого языка вам понадобится всего несколько функций.

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

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

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

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

1 голос
/ 24 июня 2009

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

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

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

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

1 голос
/ 24 июня 2009

Части речи

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

1 голос
/ 23 июня 2009

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

0 голосов
/ 23 июня 2009

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

0 голосов
/ 23 июня 2009

Используйте 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
%%
0 голосов
/ 23 июня 2009

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

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

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

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

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

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

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

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

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

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

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

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