Логический разбор текста: Логический анализ текста — Психологос

Логический анализ текста — Психологос

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

Область применения — научные и научно-популярные тексты Как читать художественную литературу — отдельный разговор, см.→

Когда автор что-то пишет, он сам еще не всегда разобрался в том, что он хочет сказать. Но — пишет. Задача читателя в этом случае — понять то, что написал бы автор, если бы разобрался сам в своих мыслях.

Чем выше ясность мышления писателя, тем более осмысленные, четкие и логически простроенные тексты он пишет. Чем лучше отработан у вас навык ЛАТ (логического анализа текста), тем лучше и быстрее вы разбираетесь в текстах. И не только в текстах письменных, но и устных — начинаете лучше понимать, что хотят сказать вам люди, с которыми вы разговариваете.

Результатно-ориентированное чтение

Нам чаще свойственно читать ради процесса чтения: мы привыкли, что чтение – это само по себе хорошо и здорово. А будет результат или нет – это второй вопрос. Мы предлагаем научиться на чтение смотреть по-другому, с результатной точки зрения. См. →

Уровни понимания текста

Текст можно понимать на разных уровнях. Три основных уровня понимания — возможность презентации (знать — не знаю, а рассказать — расскажу), уровень рабочего понимания (понимаю все основное, но в общих чертах) и глубокое понимание (понимаю в мельчайших деталях). См.→

Логический анализ текста — подразумевает глубокое понимание текста, которое складывается из следующих ступенек:

  1. Выделение основных блоков структуры текста.
  2. Выделение логических кирпичиков текста (темы и ремы).
  3. Рисунок имеющихся тем-рем — как, что и с чем связано.
  4. Если что-то оказалось не связанным — нужно связать, если чего-то не хватает — домыслить.
  5. Финальный рисунок текста — наиболее компактная и понятная схема текста (интеллект-карта)

Выборочное чтение


Логический анализ текста в серьезном варианте — вещь достаточно времяемкая. Чтобы у вас было время понимать тексты глубоко, читать тексты приходится выборочно: только то, что действительно необходимо.

Прежде чем понять, какую книгу читать вдумчиво, нужно познакомиться с книгами разными, чтобы иметь возможность сделать выбор. От 5 до 15 минут на книгу — разумное время. См.→

Презентации

Скачать ЛАТ



Приёмы анализа текста с логической стороны. Основные законы логического мышления

Приёмы анализа текста с логической стороны. Основные законы логического мышления. — Текст : электронный // Myfilology.ru – информационный филологический ресурс : [сайт]. – URL: https://myfilology.ru//162/priyomy-analiza-teksta-s-logicheskoj-storony-osnovnye-zakony-logicheskogo-myshleniya/ (дата обращения: 25.11.2021)

Приёмы анализа текста с логической стороны

Логический анализ текста необходим на всех стадиях работы над литературным произведением, необходим автору, критику, редактору. Анализировать текст, построенный логически правиль­но, обычно легко. Он всегда ясен по своей форме. Когда же логи­ческая строгость текста нарушена, его форма неизбежно неясна, высказать о нём суждение затруднительно. Ограничиться же констатацией, что мысль выражена здесь нечётко, мы не можем: от редактора требуется точное и обоснованное суждение.

Как и всякий анализ, логический анализ текста основан на мысленном делении его на части и на исследовании связей меж­ду этими частями, между смысловыми единицами текста и затекстовой действительностью и имеет два уровня: исследование ло­гики высказываний (оцениваются связи между высказываниями) и логики имён (оцениваются связи между именами внутри выска­зываний). Чтобы определить основные смысловые звенья текста, уже при первом знакомстве с ним полезно обратить внимание на то, ка­ким образом части связаны друг с другом: союзами, союзными словами, знаками препинания, какими именно. Неточное употреб­ление союзов

ибо, потому, так как, следовательно, но — вер­ный признак нелогичности мышления. Необходимо владеть тех­никой такой операции, как свёртывание суждений до возможно более простых, выраженных одним предложением, когда «каждая часть текста представляется некоторой своеобразной «смысло­вой точкой», «смысловым пунктом», в котором словно сжато всё содержание части».

В процессе «свёртывания» суждений прихо­дится отказываться от частностей, деталей, подробностей. Эта не­сложная, на первый взгляд, операция требует точности. Редактору необходим навык соотнесения смысловых звеньев на протяжении всего текста, на его участках, значительно отда­лённых друг от друга, умение восстанавливать пропущенное смыс­ловое звено. В операции свёртывания суждений всегда есть воз­можность личного толкования текста. Поэтому так трудно бывает «свернуть» текст, фиксирующий живые наблюдения автора. От­влечённые построения подвергаются этой операции гораздо легче.

Основные законы логического мышления. Первый закон – закон тождества

Закон тождества — это логический закон, согласно которому мысль (будь то понятие, суждение или умозаключение), введенная однажды в рассуждение, должна оставаться неизменной, однозначно понимаемой на протяжении всего последующего рассуждения, каким бы продолжительным оно ни являлось. То есть, сколько бы раз ни повторялась одна и та же (!) мысль, она должна пониматься одним и тем же способом.

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

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

Второй закон логического мышления называется зако­ном противоречия: не могут быть одновременно истинны­ми противоположные мысли об одном и том же предмете, взятом в одно и то же время и в одном и том же отноше­нии

Закон непротиворечия (противоречия, как он назывался в старых учебниках) — это логический закон, согласно которому не могут быть одновременно истинными взаимно исключающие друг друга мысли: «В данный момент снег идет» и «В данный момент снег не идет», «Этот цветок роза» и «Этот цветок ромашка» и т.п. С точки зрения логики объединение таких мыслей может быть только ложным, и ни в коем случае не истинным. Закон непротиворечия — суровый контролер наших рассуждений. Именно от его соблюдения зависит исходная согласованность наших мыслей, продолжающая линию закона тождества на устойчивость нашего мышления.

Логика различает два типа несовместимости мыслей:

  • а). Формальную несовместимость, которая имеет место между некоторой мыслью и ее формальным отрицанием: «Снег идет» и «Снег не идет», где одна мысль есть непосредственное формальное отрицание («не», «нет») другой. 
  • б) содержательную (предметную) несовместимость, которая имеет место в связи с несовместимостью самих признаков внутри соответствующих вещей: «Цветок — роза» и «Цветок — ромашка». Эта несовместимость определяется не по формально-логическим законам, а по законам развития самих вещей. Такая несовместимость устанавливается не логикой, а конкретными науками о соответствующих предметах и явлениях. За ошибки в определении такой (предметной) несовместимости формальная логика не несет никакой ответственности.

Закон непротиворечия распространяется на оба типа несовместимости, хотя и с оговоркой в отношении предметной несовместимости. Закон требуют, чтобы там, где противоречивость самого предмета выражается в форме формальных противоречий (так называемая антиномия-проблема) — «Вещь есть Р и не-Р одновременно» — была снята конкретным исследованием и выражена в формально-непротиворечивой форме. В противном случае логика не несет ответственности за ошибки в последующих рассуждениях и выводах относительно, таким образом, фиксируемых объектов.

Контактные противоречия в пределах одной фразы или во фразах, соседствующих друг с дру­гом, обнаружить нетрудно, но

противоречия дистантные, отде­лённые друг от друга значительными по объёму участками текста, часто проходят мимо внимания редактора. Чтобы обнаружить так называемые неявные противоре­чия, редактору недостаточно просто сопоставить высказывания, от него требуются дополнительные мыслительные операции. При­ведем элементарные примеры, где следует провести подсчёт, что­бы выявить противоречия.  Мимо внимания редактора не могут пройти даже неполные противоречия. Малейшая логическая неточность в тексте дол­жна быть им замечена.

Закон исключённого третьего. Третий закон логического мышления — закон исклю­чённого третьего — гласит: из двух противоречащих выс­казываний в одно и то же время, в одном и том же отно­шении одно непременно истинно

Третьего не дано. Аристо­тель формулировал этот закон так: не может быть ничего посредине между двумя противоречащими суждениями. Третий закон обеспечивает связность, непротиворечивость мыс­ли, служит основанием для выбора истинного суждения. Точность подбора противоречащих высказываний, чёткость их формулировки, конструктивная ясность текста делают очевидным действие этого закона, способствуют логической определённости из­ложения, позволяют достичь последовательности развития мысли. Непременное условие соблюдения третьего закона логики – сопоставляемые высказывания должны быть действительно про­тиворечивыми, т. е. такими, между которыми нет и не может быть среднего, третьего, промежуточного понятия. Они должны исклю­чать друг друга.

Когда автор очерка о лётчике пишет: «Человек на земле может быть и мягким, и деликатным, а в полёте – соб­ранным, волевым», он нарушает этот закон. Перечисляемые каче­ства характера не исключают друг друга. Уязвимо с позиций логики и следующее суждение, адресованное молодым журналистам: «Журналист в результате своего «расследования» явлений жиз­ни выступает либо в поддержку всего нового, передового, прогрессивного … либо непримиримым борцом со всем отжив­шим, со всеми социальными болячками…» Противопоставление в данном случае явно не состоялось. Формулировка альтернативы – приём, присущий энергичной манере изложения: мысль требует ясности формы, сам приём – точности исполнения.

В дискуссионных материалах, в острых интервью это проявляется особенно отчётливо. Так, задав од­нажды ведущему популярной в начале 90-х годов передачи «600 секунд» А. Невзорову вопрос: какой он человек – добрый или злой? – тележурналист явно рассчитывал получить однозначный ответ. Однако такой ход беседы не устраивал Невзорова, и он легко ушёл от ответа: «Опять-таки очень абстрактное представ­ление – добрый или злой. Вот Пожарский с Вашей точки зрения, добрый или злой?» В чём просчёт задававшего вопрос? Понятия «добрый» и «злой» с позиций строгой логики не являются проти­воречащими, они не исключают полностью друг друга. В опреде­лённых условиях между ними возможно существование некоего третьего понятия. Этим и пользуется Невзоров, переводя разго­вор на оценку известной всем личности Пожарского и собираясь, видимо, сообщить сведения, которые позволят выявить промежу­точное звено между понятиями «добрый» и «злой», чтобы дока­зать несостоятельность предъявленной ему альтернативы. Бесе­да изменила своё русло.

Третий закон логического мышления важен для редактора и как инструмент при профессиональной оценке текста: отвергая один вариант текста, принимая другой, редактор опирается на этот закон.

Четвёртый закон логического мышления — закон достаточного основания — требует, чтобы всякая истин­ная мысль была обоснована другими мыслями, истин­ность которых доказана

Соблюдением этого закона достига­ется обоснованность мышления, обязательное условие мышления правильного. Логика высказываний считает обоснованность мыш­ления общим методологическим требованием и рассматривает ряд законов, обеспечивающих его выполнение (закон двойного отрицания, тавтологии, симплификации, конъюнкции и др.) В любом рассуждении наши мысли должны быть внутренне связаны друг с другом, вытекать одна из другой, обосновывать одна другую. Истинность суждений должна быть подтверждена надёжными доказательствами.

Четвёртый закон формулирует это требование в наиболее общем виде. Достаточность основания истинности суждений в каждом конкретном случае – предмет рассмотрения специальных наук, логическая обоснованность – необходимое качество каждого журналистского выступления. Именно обоснованности суждений недостаёт газетной рецен­зии на книгу И. Долгополова «Рассказы о художниках».

Отрывок из этой рецензии мы приводим ниже: Тот, кто читает журнал «Огонёк», не может не обратить внимание на серию статей, посвящённых творчеству художников. Многие из этих статей – точнее рассказов – принадлежат Игорю Долгополову. Разумеется, любой такой рассказ должен не­сти в себе ту дозу информации, без которой нет документального рассказа, а тем более – статьи. То, что пишет И. Долгополое, – это, скорее всего, именно рассказы. То есть необходимая информа­ция сочетается с формой подачи её. А эта форма более всего напоминает рассказ. Поэтому совершенно правомерно, что тридцать рассказов И. Долгополова оказались под одним красивым тёмно-зелёным переплётом. И название книги «Рассказы о художниках» – соответствует содержанию её.

В этом отрывке из 28 газетных строк логических неточностей много. В частности, автору следует задать вопрос: в чём же он усматривает правомерность того, что тридцать рассказов Долгопо­лова оказались под одним тёмно-зелёным переплётом? И потому, что ответить на этот вопрос нетрудно, ещё более досадна небреж­ность изложения, затемняющая совсем не сложную мысль и запу­тывающая читателя. Причиной недоказательности изложения могут быть не толь­ко поверхностность знаний и небрежность формулировок, но и автоматизм мышления, когда, например, журналист прибегает к привычным и, казалось бы, проверенным схемам типа «раньше – теперь».

Обыденное сознание часто мешает пишущему вникнуть в суть явления, вскрыть причины происходящего. Иллюзия пол­ной очевидности для журналиста всегда опасна. Но особенно опас­на всякая попытка подогнать факты под некую схему, какой бы «надежной» она ни представлялась. Насилие над материалом неизбежно проявит себя.

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

07.09.2016, 7547 просмотров.

Логический анализ текста

Примеры имён в тексте: психолог, социолог., непосредственность, индивидуальность, слабость.

Примеры простых высказываний:

  1. В каждой жизни присутствуют последовательные стадии роста и развития.

  2. Каждый шаг важен, и на каждый требуется время.

  3. Ни единого шага пропустить нельзя.

Примеры сложных высказываний:

  1. Если в любой сфере деятельности я по десятибалльной шкале нахожусь на отметке «два» и хочу передвинуться на отметку «пять», то сначала надо сделать шаг к отметке «три».

  2. Слушать — это значит быть терпеливым, открытым, иметь желание понять, то есть иметь свойства высоко развитого характера.

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

Связи между именами и высказываниями

Умножение имён. Обозначается — Р.Q, читается Р и Q:

  • работы и роста: Р — «работы», Q — «роста»;

  • непосредственности и индивидуальности: Р — «непосредственности»,Q — «индивидуальности»;

  • психолог и социолог: Р — «психолог»,Q — «социолог»;

  • роста и развития: Р— «роста»,Q — «развития»;

  • характера и эмоционального развития: Р — «характера»,Q — «эмоционального развития».

Сложение имён. Обозначается — Р + Q, читается Р или Q:

  • игре на фортепиано или эффективному общению с коллегой по работе: Р — «игре на фортепиано», Q — «эффективному общению с коллегой по работе»;

  • к сфере человеческих отношений или к характеру личности: Р — «сфере человеческих отношений»,Q — «характеру личности»;

  • друзьями или коллегами: Р— «друзьями»,Q — «коллегами»;

  • с теннисом или фортепиано: Р — «теннисом»,Q — «фортепиано»;

  • огромный рост или физическая мощь: Р — «огромный рост»,Q — «физическая мощь».

Отрицание имен. Обозначается Р, читается не Р:

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

    • Р — «не осознавали своей индивидуальности»;

    • Р — «не испытывали чувства собственного достоинства».

Постоянные логики высказываний

Импликация. Обозначается pq, читается если p, то q:

  • «Но дажееслимы сможем понять это,топринять это и жить в соответствии с этим пониманием окажется еще более трудной задачей», где р«мы сможем понять это»,q«принять это и жить в соответствии с этим пониманием окажется еще более трудной задачей»;

  • «Еслив любой сфере деятельности я по десятибалльной шкале нахожусь на отметке «два» и хочу передвинуться на отметку «пять»,тосначала надо сделать шаг к отметке три», гдер«в любой сфере деятельности я по десятибалльной шкале нахожусь на отметке «два» и хочу передвинуться на отметку «пять»»,q«сначала надо сделать шаг к отметке три». q, читается p и q:

    • «»Путешествие в тысячу миль начинается с первого шага», ив каждый момент может делаться только один шаг»,где р«»Путешествие в тысячу миль начинается с первого шага»»,q«в каждый момент может делаться только один шаг»;

    • «Сотрудничество уступает место страху, иоба человека становятся более эгоистичными и агрессивными», гдер«Сотрудничество уступает место страху»,q«оба человека становятся более эгоистичными и агрессивными».

    Слабая дизъюнкция. Обозначается р ν q, читается р или q:

    • «Вы вынуждены поехать иливы решили поехать?», гдер«Вы вынуждены поехать»,q«вы решили поехать»;

    • «Приобретите инструмент планирования четвертого поколения илипреобразуйте в него ваш нынешний инструмент», гдер«приобретите инструмент планирования четвертого поколения»,q«преобразуйте в него ваш нынешний инструмент».

    Эквивалентность. Обозначается р = q, читается если и только если р, то q:

    • «Истинного величия можно достичь, толькообладая менталитетом достаточности, когда человек действует бескорыстно, проявляя взаимоуважение для достижения взаимной выгоды», гдер«обладая менталитетом достаточности»,q«истинного величия можно достичь».

    Сильная дизъюнкция. Обозначается р × q, читается или р, или q:

    • «Если бы я был более зрелым, я мог бы положиться на свою внутреннюю силу — на свое понимание роста и необходимости делиться, на свою способность любить и воспитывать, — и позволил бы дочери самой выбрать – хочет она делиться или нет», гдер «хочет она делиться»,q — «нет».

    «Логика речи. Подготовительный этап подготовки публичного выступления»

    Просмотр содержимого документа
    «»Логика речи. Подготовительный этап подготовки публичного выступления»»

    Логика речи

    Подготовительный этап подготовки публичного выступления

    Логический разбор текста

    • Основное средство достижения выразительности чтения

    Анализ текста

    Моя сверхзадача

    идея

    тема

    Душа произведения.

    Отвечает на вопрос:

    Для чего автор создал произведение?

    Что хотел сказать?

    Ради чего я читаю это произведение

    ( материал)?

    Чего я хочу добиться от слушателя?

    Тело произведения.

    Отвечает на вопросы: О чём? Про что?

    Законы логической речи

    Законы логической речи

    Законы сценической речи

    • Помогают понять мысль автора
    • и верно передать её смысл
    • в звучащей речи.

    Логический разбор

    • Основан на законах грамматики :
    • слова, составляющие предложение, связаны по смыслу друг с другом.

    по смыслу друг с другом слова отделяются

    • логическими паузами .
    • Они помогают верно передать мысль фразы.

    Речевой такт

    • Скоро / луна и звёзды / потонут в густом тумане.

    1 такт 2 такт 3 такт

    Простить нельзя сослать в Сибирь

    Простить / нельзя сослать в Сибирь .

    Простить нельзя / сослать в Сибирь.

    Изменение высоты голоса

    рождает

    • инто нацио нное разн ообр азие речи.

    Четыре группы пауз:

    Грамматическая пауза

    Логическая пауза

    Люфтпауза

    Психологическая пауза

    Грамматическая пауза

    • Ставится на месте знаков препинания.
    • Каждый знак препинания интонируется повышением или понижение голоса
    • в зависимости от значения знака препинания.

    Логическая пауза

    • Ставится между группой подлежащего и группой сказуемого
    • Перед и после обстоятельственных слов
    • Перед соединительными союзами «И», « ИЛИ», «ДА»

    Марья Гавриловна закрыла книгу и потупила глаза в знак согласия .

    • Марья Гавриловна (кто?) I закрыла книгу (что сделала?) I и потупила глаза (что еще сделала?) I в знак согласия (почему?).

    Осенью семейство Ростовых вернулось в Москву .

    • Осенью (когда?—обстоятельство времени) I семейство Рос­товых ( кто?—группа подлежащего) I вернулось в Москву (что сделало?—группа сказуемого, в нее вошло обстоятельство ме­ста «в Москву») .

    Облако бензиновой гари I от ав­тобуса скрыло I их от Ольги Вячеславовны.

    • ПРАВИЛЬНО: Облако бензиновой гари от автобуса (группа подлежащего) I скрыло их от Ольги Вячеславовны (группа ска­зуемого) .

    Обозначение при разборе:

    • / — пауза между речевыми тактами или тесно связанными между собой по смыслу предложениями;
    • // -более длительная между речевы­ми тактами или между предложениями;
    • /// — еще более длительная пауза (между предложениями, смысловы­ми и сюжетными кусками).

    Люфтпауза

    • паузы для добора воз­духа— «воздушные»
    • от немецкого Luft —воздух)

    Люфпауза

    • — очень короткая ;
    • — исполь­зуется как дополнительная пауза перед словом, которое мы хотим почему-либо выделить;
    • — такая пауза может помочь уточне­нию. Она полностью зависит от намерений и задач исполнителя.

    В тексте обозначается ‘ или v

    • «Судя по голосу—I певица I была ‘ пьяная».
    • (М.Горький. «Страсти мордаста».)

    Психологическая пауза

    • Не подчиняется законам
    • логиче­ского чтения текста.
    • Она всецело относится к области словесного действия.

    Обозначается многоточием:

    • Я никого не люблю и… уже не полюблю.
    • Близко к психологической паузе
    • стоит так называемая
    • пауза умолчания
    • или
    • Пауза прерванной речи ,
    • когда недосказанные слова заменяются многоточием.

    Жена его… впрочем, они были совершенно довольны друг другом.

    (Н. Гоголь. «Мертвые души».)

    Так же:

    • Пауза начала – собрат внимание, освоиться.
    • Пауза конца- закрепить эффект, усилить эмоциональное впечатление

    Логическое ударение:

    • выделение с помощью звуковых средств слова или группы слов.

    Цель ударения :

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

    Пример:

    • «Теперь конец СЕНТЯБРЯ, а ветлы еще не пожелтели».
    • (В. Солоухин. «Капля росы».)

    Как выделить?

    • — с помощью усиления или ослабле­ния звука;
    • — повышения или понижения тона на ударяемом слове;
    • — замедления темпа речи при произнесении слова или группы слов ;
    • -в некоторых случаях на ударном слове может быть сдела­но подчеркивающее ударение , т. е. такое ударение, которое резко выделяет ударное слово, вызывая у слушателя ощущение, что вне данного предложения имеется противопоставление. В этом случае повышение (или понижение) голоса на ударном слове бывает более резкое и сильное, чем при обычном ударении.
    • « Глаза у него были НЕСРАВНЕННЫЕ — большие, чер­ные, с таким взглядом, который, когда мы встречались с ним, казалось, только и составляет единственное, что есть в данную минуту в мире. Ничего, казалось, нет сейчас вокруг нас, только этот ВЗГЛЯД существует.
    • Когда я вспоминаю Маяковского, я тотчас же вижу эти глаза — сквозь обои, сквозь листву. Они на меня смотрят, и мне кажется, что в мире становится тихо, таинственно.
    • Что это за взгляд? ЭТО ВЗГЛЯД ГЕ НИ Я».
    • ( Ю. Олеша. «Ни дня без строчки».)

    Спасибо за внимание! Терпения и удачи !

    Роль логического программирования, и стоит ли планировать его изучение на 2021-й

    Начну, пожалуй, с представления читателя этой статьи, так как ничто не приковывает внимание к тексту более, чем сопереживание главному герою, тем более, в его роли сейчас выступаете Вы. Вероятно, услышав или прочитав однажды словосочетание «логическое программирование» и преисполнившись интересом, Вы как настоящий или будущий программист направились в Google. Первая ссылка, разумеется, ведёт на Википедию — читаем определение:

    Логи́ческое программи́рование — парадигма программирования, основанная на автоматическом доказательстве теорем, а также раздел дискретной математики, изучающий принципы логического вывода информации на основе заданных фактов и правил вывода. Логическое программирование основано на теории и аппарате математической логики с использованием математических принципов резолюций.

    «Мда» — думаете Вы, и этим все сказано. Сложно! И тут наш отважный герой должен бы был перейти по второй ссылке, но я позволю себе сделать небольшую вставку, описав главное действующее лицо: Вы, по моей задумке, новичок в программировании, а даже если и нет, то точно не знакомы с логическим его обличием. Если же читатель уже несколько (или даже много) искушен знаниями в этой области, то рекомендую прочитать статью Что такое логическое программирование и зачем оно нам нужно, раз уж в вас горит интерес и любопытство к теме, а изучение материала ниже оставьте менее опытным коллегам.

    Итак, пришло время второй ссылки. Что это будет? Статья на Хабре? Может быть статья на ином ресурсе? Прочитав пару первых абзацев на разных сайтах, вы, скорее всего, мало что поймете, так как, во-первых, материал обычно ориентирован на знающего читателя, во-вторых, хорошей и понятной информации по теме не так много в русскоязычном интернете, в-третьих, там почему-то постоянно речь идёт о некоем «прологе» (речь о языке программирования Prolog, разумеется), но сам язык, кажется, использует мало кто (почётное 35 место в рейтинге TIOBE). Однако наш герой не теряет мотивации и, спустя некоторое время, натыкается на эту самую статью, желая, все-таки понять:

    • Что такое логическое программирование

    • Какова история его создания и фундаментальные основы (серьезно, какому новичку это может быть интересно?)

    • Зачем и где его применяют

    • Стоит ли лично вам его изучать

    Что ж, постараюсь ответить просто и понятно, обходя страшные термины и не вспоминая исторических личностей.

    Что такое логическое программирование

    В школе на уроках информатики многие, если не все, слышали про Pascal (а кто-то даже писал на нем). Многие также могли слышать про Python, C/C++/C#, Java. Обычно программирование начинают изучать именно с языков из этого набора, поэтому все привыкли, что программа выглядит как-то так:

    Начать
    Команда1
    Команда2
    Если УСЛОВИЕ
      Команда3
    Иначе
      Команда4
    Закончить

    Этот яркий, но малоинформативный пример призван показать, что обычно команды выполняются одна за другой, причем мы ожидаем, что следующие инструкции (команды) могут использовать результат работы предыдущих. Также мы можем описывать собственные команды, код которых будет написан подобным образом. Остановимся на том, что как завещал Фон Нейман, так компьютер и работает. Машина глупа, она делает то, что скажет программист, по четкому алгоритму, последовательно выполняя инструкции. Не может же, в конце концов, компьютер, подобно мыслящему живому существу, делать выводы, строить ассоциативные ряды… Или может?

    Давайте устроимся поудобнее рядом со своим компьютером и порассуждаем о жизни и смерти вместе с Аристотелем:

    Всякий человек смертен.

    Сократ — человек.

    Следовательно, Сократ смертен.

    Звучит логично. Но есть ли способ научить компьютер делать выводы как Аристотель? Конечно! И вот тут мы вспомним о Prolog-e, который так часто мелькает при поиске информации о логическом программировании. Как несложно догадаться, Prolog (Пролог) является самым популярным чисто логическим языком программирования. Давайте рассуждения об этом языке оставим на следующие разделы статьи, а пока что продемонстрируем «фишки» логических языков, используя Пролог.

    Напишем небольшую программу, где перечислим, кто является людьми (ограничимся тремя) и добавим правило «всякий человек смертен»:

    % Всё, что после знака процента в строке - комментарии
    human('Plato'). % Платон - человек
    human('Socrates'). % Сократ - тоже человек
    human('Aristotle'). % Конечно, человеком был и Аристотель
    % ...и др. философы
    
    mortal(X) :- human(X). % Читаем так: "X смертен, если X - человек"

    Что ж, давайте спросим у компьютера, смертен ли Сократ:

    ?- mortal('Socrates').
    true.

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

    Так, теперь стоит успокоиться и разобраться, что же произошло. Вначале мы записали т. н. факты, то есть знания нашей программы о мире. В нашем случае ей известно лишь то, что Платон, Сократ и Аристотель — люди. Но что за странная запись «human(‘Socrates’).» и почему это выглядит как функция? На самом деле «human» и «mortal» — предикаты от одной переменной. Да, тут уже пошли термины, но постараюсь объяснять их просто и понятно для тех, кто привык к императивному нормальному программированию.

    Логическое программирование основано на логике предикатов. Предикатом называется (и здесь я позволю себе вольность) функция от нуля или более переменных, возвращающая значение логического типа (истина (true) или ложь (false)). Факт — это предикат с определенным набором параметров и заведомо известным значением.

    % слова с большой буквы Prolog считает переменными, поэтому их следует заключать в кавычки
    like('Petya', 'Milk'). % программа знает, что Петя любит молоко
    good('Kesha'). % Кеша хороший
    number_of_sides('Triangle', 3). % у треугольника три вершины
    
    like('Misha', X). % не является фактом, так как значение переменной X не определено

    Помимо фактов в логической программе присутствуют правила вывода. В данном случае это «mortal(X) :- human(X).». Набор правил вывода — это знания нашей программы о том, как выводить (искать/подбирать) решение. Правила записываются следующим образом:

    a(X,Y,Z) :- b(X), c(Y,Z), d().

    Предикат a от трех аргументов вернет истину, если удастся доказать истинность предикатов b, c и d. Читаются правила справа налево следующим образом: «Если b от X истинно И c от Y, Z истинно И d истинно, то a от X, Y, Z истинно».

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

    % Опишем набор фактов о том, кто что обычно ест на завтрак в семье Пети
    eat(father, cheese).
    eat(father, apple).
    eat(father, melon).
    eat(mother, meat).
    eat(sister, meat).
    eat('Petya', cheese).
    eat(brother, orange).

    Теперь начнём делать запросы к программе (всё те же предикаты):

    ?- eat(father, apple). % ест ли отец яблоки
    true.
    
    ?- eat(father, meat).  % ест ли отец мясо
    false.
    
    ?- eat(sister, X). % что ест сестра
    X = meat.
    
    ?- eat(X, cheese). % кто ест сыр
    X = father ;
    X = 'Petya'.
    
    ?- eat(X, Y). % кто что ест
    X = father,
    Y = cheese ;
    X = father,
    Y = apple ;
    X = father,
    Y = melon ;
    X = mother,
    Y = meat ;
    X = sister,
    Y = meat ;
    X = 'Petya',
    Y = cheese ;
    X = brother,
    Y = orange.

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

    Какие задачи и как можно решать с помощью логического программирования

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

    d(X,X,1) :- !. % производная X по X = 1
    d(T,X,0) :- atomic(T). % производная константы = 0
    d(U+V,X,DU+DV) :- d(U,X,DU), d(V,X,DV). % производная суммы = сумме производных
    d(U-V,X,DU-DV) :- d(U,X,DU), d(V,X,DV). 
    d(-T,X,-R) :- d(T,X,R).
    d(C*U,X,C*W) :- atomic(C), C\=X, !, d(U,X,W). % производная константы, умноженной на выражение = константе на производную от выражения
    d(U*V,X,Vd*U+Ud*V) :- d(U,X,Ud), d(V,X,Vd). % производная произведения
    d(U/V,X,(Ud*V-Vd*U)/(V*V)) :- d(U,X,Ud), d(V,X,Vd). 

    Запустим:

    ?- d((x-1)/(x+1),x,R).   
    R =  ((1-0)*(x+1)-(1+0)*(x-1))/((x+1)*(x+1)).

    Пусть производная получилась довольно громоздкой, но мы и не ставили цель её упростить. Главное, из примера видно, что правила вывода производной на Prolog-е описываются очень близким образом к их математическому представлению. Чтобы сделать подобное на привычных языках программирования, пришлось бы вводить понятие дерева выражений, описывать каждое правило в виде функции и т. д. Тут же мы обошлись 8-ю строками. Но здесь важно остановиться и задуматься: компьютер не начал работать как-то иначе, он все ещё обрабатывает последовательности команд. Стало быть, те самые деревья, которые где-то все-таки должны быть зашиты, чтобы программа работала, действительно присутствуют, но в неявном виде. Деревья эти именуют «деревьями вывода», именно они позволяют подбирать нужные значения переменных, перебирая все возможные варианты их значений (существует механизм отсечения, который является надстройкой над логической основой языка, но не будем об этом).

    Давайте на простом примере рассмотрим, что из себя представляет перебор, а затем то, чем он может быть опасен.

    speciality(X,tech_translator) :- studied_languages(X), studied_technical(X). % X - технический переводчик, если изучал языки и технические предметы
    speciality(X,programmer) :- studied(X,mathematics), studied(X, compscience). % X - программист, если изучал математику и компьютерные науки
    speciality(X,lit_translator) :- studied_languages(X), studied(X,literature). % X - литературный переводчик, если изучал языки
    studied_technical(X) :- studied(X,mathematics). % X изучал технические предметы, если изучал математику
    studied_technical(X) :- studied(X,compscience). % ...или компьютерные науки
    studied_languages(X) :- studied(X,english). % X изучал языки, если изучал английский
    studied_languages(X) :- studied(X,german). % ...или немецкий
    
    studied(petya,mathematics). % Петя изучал математику
    studied(petya,compscience). % ...компьютерные науки
    studied(petya,english). % ...и английски
    studied(vasya,german). % Вася изучал немецкий
    studied(vasya,literature). %...и литературу

    Спросим, кто из ребят, известных компьютеру — технический переводчик:

    ?- speciality(X,tech_translator).
    X = petya ;
    X = petya ;
    false.

    Ага…то есть Петя, Петя и ложь… Что-то не так, подумает программист и попробует разобраться. На самом деле, перебирая все варианты значений X, Пролог пройдёт по такому дереву:

    Дерево будет обходиться в глубину, то есть сначала рассматривается всё левое поддерево для каждой вершины, затем правое. Таким образом, Пролог дважды докажет, что Петя — технический переводчик, но больше решений не найдёт и вернёт false. Стало быть, половина дерева нам, в общем-то, была не нужна. В данном случае, перебор не выглядит особенно страшным, всего-то обработали лишнюю запись в базе. Чтобы показать «опасность» перебора, рассмотрим другой пример:

    Представим, что перед нами в ячейках расположены три чёрных и три белых шара (как на картинке выше), которые требуется поменять местами. За один ход шар может или передвинуться в соседнюю пустую клетку, или в пустую клетку за соседним шаром («перепрыгнуть» его). Решать будем поиском в ширину в пространстве состояний (состоянием будем считать расположение шаров в ячейках). Суть этого метода заключается в том, что мы ищем все пути длины 1, затем все их продления, затем продления продлений и т. д., пока не найдем целевую вершину (состояние). Почему поиск в ширину? Он первым делом выведет самый оптимальный путь, то есть самый короткий. Как может выглядеть код решения:

    % Обозначения: w - белый шар, b - чёрный, e - пустая ячейка
    is_ball(w). % w - шар
    is_ball(b). % b - шар
    
    near([X,e|T],[e,X|T]) :- is_ball(X). % если фишка рядом с пустой ячейкой, то можно переместиться
    near([e,X|T],[X,e|T]) :- is_ball(X).
    jump([X,Y,e|T],[e,Y,X|T]) :- is_ball(X), is_ball(Y). % если за соседним шаром есть пустая ячейка, то можно переместиться
    jump([e,Y,X|T],[X,Y,e|T]) :- is_ball(X), is_ball(Y).
    
    % предикат перемещения. Мы или рассматриваем первые элементы списка, или убираем первый элемент и повторяем операцию
    move(L1,L2) :- near(L1,L2). 
    move(L1,L2) :- jump(L1,L2).
    move([X|T1],[X|T2]) :- move(T1,T2).
    
    % предикат продления текущего пути. Если из состояния X можно перейти в состояние Y и
    % Y не содержится в текущем пути, то Y - удачное продление
    prolong([X|T],[Y,X|T]) :- move(X,Y), not(member(Y,[X|T])).
    
    % Первый аргумент - очередь путей, второй - целевое состояние, третий - результат, то есть найденный путь
    bdth([[X|T]|_],X,R) :- reverse([X|T], R). % Поиск в ширину нашел решение, если первый элемент пути совпадает с целью (путь наращивается с начала, так что перевернем результат)
    bdth([P|QI],Y,R) :- bagof(Z,prolong(P,Z),T), append(QI,T,QO), !, bdth(QO,Y,R). % Ищем все возможные продления первого пути и кладём в очередь, рекурсивно запускаем поиск
    bdth([_|T],Y,R) :- bdth(T,Y,R). % Если продлений на предыдущем шаге не нашлось, то есть bagof вернул false, убираем первый путь из очереди
    bsearch(X,Y,R) :- bdth([[X]],Y,R). % Удобная обёртка над предикатом bdth
    
    % Предикат, который решает нашу задачу и выводит результат и длину найденного пути на экран
    solve :- bsearch([w,w,w,e,b,b,b],[b,b,b,e,w,w,w],P), write(P), nl, length(P, Len), write(Len), nl.

    Если вы попытаетесь вызвать предикат solve, то, в лучшем случае увидите ошибку, в худшем — среда зависнет. Дерево здесь (с учётом лежащих в памяти путей) настолько велико, что переполнит стек, так и не подарив нам ответа. Ну и что — скажете вы, это же может происходить и в императивных (процедурных (обычных)) языках программирования. Конечно. Но, повторюсь, на решение той же задачи на Питоне или Си (без использования библиотек) ушло бы на порядки больше времени. Давайте для полноты картины я приведу решение данной проблемы, а после перейдем к тому, какие же задачи решаются подобным образом.

    Со стороны улучшения алгоритма можно предложить использовать поиск в глубину. Но как же, он ведь не даст оптимального результата? Сделаем просто: ограничим глубину поиска. Так мы точно не забьём стек и, возможно, получим ответ. Поступим так: проверим, есть ли пути длины 1, затем длины 2, затем длины 4 и т. д. Получим так называемый поиск с итерационным заглублением:

    % Первый аргумент - текущий путь, второй - целевое состояние, третий - результат, то есть найденный путь
    dpth_id([X|T],X,R,0) :- reverse([X|T], R). % Успешное окончание поиска
    dpth_id(P,Y,R,N) :- N > 0, prolong(P,P1), N1 is N - 1, dpth_id(P1,Y,R,N1). % Если счётчик >0, то уменьшаем его и продолжаем поиск рекурсивно
    generator(1). % Изначально предикат вернет 1
    generator(N) :- generator(M), N is M + 1. % Рекурсивно получаем 2, 3, 4 и т. д.
    isearch(X,Y,R) :- generator(D), dpth_id([X],Y,R,D). % Удобная обертка, которая будет вызывать поиск от каждого натурального значения глубины.

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

    С другой стороны, задачу можно было бы решить, не меняя код поиска, а лишь изменив правила перемещения шаров. Обратим внимание, что нам заранее известны входные и выходные данные. Приглядевшись становится понятно, что нет никакого смысла в движении фишек «назад». Действительно, если чёрным нужно встать в правые позиции, то какой смысл делать ходы влево? Перепишем предикаты движения:

    near([w,e|T],[e,w|T]).
    near([e,b|T],[b,e|T]).
    jump([w,X,e|T],[e,X,w|T]) :- is_ball(X).
    jump([e,X,b|T],[b,X,e|T]) :- is_ball(X).

    Хм, код стал даже проще. Запустив мы убедимся, что поиск (оба варианта), во-первых, работает, во-вторых, работает быстро, в-третьих, работает быстро и выводит результат. Это успех. Мало того, что мы решили задачку, только что был создан самый настоящий искусственный интеллект. Программа получает входные данные и желаемый результат, а затем сама ищет, как его достигнуть. Да, это однозначно успех.

    Зачем и где применяют логическое программирование

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

    • Анализ естественного языка: Пример с производной — классический пример разбора выражений. Но если мы заменим арифметические операторы, переменные и числа на слова, добавим правила, скажем, английского языка, то сможем получить программу, разбирающую текст на структурные элементы. Занимательно, что одновременно мы получим и программу, способную генерировать текст. Но если логическое программирование можно удобно и эффективно использовать для анализа и разбора текста, то в задачах генерации качественного текста скорее придется обращаться к нейросетям. Тут важно отметить, что рассуждая об анализе и генерации предложений нельзя не упомянуть сложность решения подобных задач. Человек при составлении и восприятии текста ориентируется не только на набор слов и их значений, но и на свою картину мира. К примеру, если в базе лежит факт «Миша починил Маше компьютер», то на вопрос «Разбирается ли Миша в компьютерах?» программа не сможет ответить, не смотря даже на то, что решение вроде как «на поверхности». Именно из-за низкой скорости и особенностей использования на чисто логических языках не занимаются тем, что мы ждем увидеть, загуглив «нейросети» (поиск котиков на картинке, например, не для Пролога). Но вот задачи синтаксического разбора, текстовой аналитики и т. п. на логических языках решать очень даже комфортно.

    • Поиск решений: Задача с Петей и Васей, а также задача с шарами — примеры поиска решений. Представим, что у нас есть набор знаний о некоторой системе и нам нужно понять, можно ли тем или иным путем её взломать (обойти защиту). Это можно достаточно лаконичным образом описать на логических языках и запустить процесс поиска решений. Помимо переборных задач, хорошо будут решаться те, где требуются логические рассуждения (анализ кода или, опять же, естественного текста).

    • Мета-программирование: С помощью того же Пролога можно описать свой собственный язык, соответственно, со своими правилами. Это достаточно важный момент для многих проектов, где сталкиваются специалисты разных сфер. Например, стоит задача анализа неких химических данных. В команде работают химик и программист. Получается, программист должен постоянно советоваться с химиком о том, что и как ему делать, ведь химическим формулам то в IT не учат. А разговаривать — это, в общем-то, не особенно приятно, особенно если кто-то умнее тебя. И тут программисту было бы очень удобно написать простой язык, на котором химик сможет сам записать свои формулы, не трогая разработчика. Вот тут и пригодится логическое программирование.

    И тут крайне важно отметить, что решения на логических языках оказываются столь же неэффективны, сколько удобны (если речь не идёт о нишевых, специализированных решениях). Программа на императивном языке всегда обгонит аналогичную программу на логическом, но затраты на написание кода в ряде случаев (в том числе описанных выше) падают в разы. На практике вы вряд ли столкнетесь именно с Prolog-ом. Он, конечно, выразителен (можно описывать сложные вещи просто), хорошо расширяется, на нем легко писать надежный код и решать определенные задачи (в т. ч. просто логические задачки), но есть и ряд недостатков: пролог сильно уступает по скорости императивным языкам, а также не особенно поддерживается и не развивается, что делает его применение на практике практически невозможным.

    Стоит ли планировать его изучение на 2021-й

    Тут оставлю своё субъективное мнение, разделённое на две части:

    • Если вы только-только делаете первые шаги в программировании, то браться за логическое программирование не стоит, поскольку на практике вы будете практически всегда писать код, используя императивные языки, и их изучение — основа вашей будущей специальности. Учиться разрабатывать программы сразу и в императивной, и в декларативной (описываем задачу и результат, а не как его получить) парадигмах, как по мне, малоэффективно.

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

    И здесь остаётся лишь пожелать продуктивного 2021-го года!

    Хочется выразить особую благодарность Дмитрию Сошникову за знакомство с этой удивительной парадигмой.

    10 логических задач для нестандартного мышления / Newtonew: новости сетевого образования

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

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

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

    Используйте комплексный подход

    Среди всего многообразия логических задач часто дети выбирают себе пару любимых категорий и погружаются в их решение. Достаточно ли этого?

    Наверняка большинство из нас хотя бы раз проходили тесты на уровень логики. Большинство их составлено из одних силлогизмов или вопросов с подвохом. Мы не предлагаем подобные тесты, потому что точно знаем, что определить уровень развития логического мышления с помощью десятка или двух вопросов, даже приблизительно, невозможно. Так же, как и развить нестандартное мышление, решая только отдельные типы логических задач.

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

    Логика — это вкусняшка для ума

    Именно так написали на доске ученики перед началом одного из занятий нашего кружка по логике. В чём же прелесть логических задач?

    • они будут одинаково интересны и увлечённым математикой детям, и «гуманитариям»;
    • многие из них не требуют знаний школьной программы;
    • их может решать даже дошкольник без навыков чтения (например, судоку, ребусы, головоломки со спичками, «шестерёнки» и другие задачи в картинках).

    Дети любят решать логические задачи и загадки. Им это интересно! Когда я работала в школе, я видела, что ребята справляются с программой, механически запоминая способ решения тех или иных типовых задач.

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

     

    Битно Галина Михайловна

    завуч LogicLike, учитель высшей категории

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

    Попробуйте сами

    В онлайн-платформе Logiclike, созданной для развития логики и математических способностей у детей 5-12 лет, авторы постарались реализовать всё то, чего зачастую так не хватает и ученикам, и учителям в школьных программах. Системность, вовлечение, интерактивность, наглядность, мотивация… Но первым делом это — пища для ума, та самая «вкусняшка», которая заставляет ребенка думать, рассуждать, проверять свои силы, проявлять творческий подход и радоваться, когда удаётся найти правильное решение.

    Рекомендации от методистов и учителей LogicLike:

    • Хотите развить у ребенка нестандартное мышление и гибкую логику – давайте ему хорошую зарядку для ума в виде разнообразных логических задач, для решения которых нужно использовать разные логические законы и методы решения (метод с конца, табличный метод, с помощью графов или кругов Эйлера и т.д.)
    • Подходите к обучению системно: от теории к задачам, от простого к сложному, от знакомства с новыми типами заданий к рефлексии.
    • Учитывайте специфику мышления у детей младшего школьного возраста – используйте визуальные образы и наглядные материалы.
    • Важно не навязывать детям способ решения, а стараться проводить разбор так, чтобы они сами путем логических рассуждений нашли правильный ответ.
    • Внедряйте игровые элементы в процесс обучения, используйте обучающие возможности IT.
    • Занятия логикой, как и спортивные тренировки, нуждаются в регулярности и постепенном повышении сложности задач.

    Занимайтесь вместе с ребенком и с удовольствием!

     

    27 января 2017, 12:00
    Мнение автора может не совпадать с позицией редакции.

    Скопировать ссылку

    АВТОРСКАЯ КОЛОНКА

    ЛогикЛайк

    LogicLike.com — образовательная онлайн-платформа для детей 5-12 лет, их родителей, а также любознательных взрослых. Мы рассказываем, как тренировать мышление и математические способности, публикуем логические задачи и тесты, делимся мыслями об образовании.

    Нашли опечатку? Выделите фрагмент и нажмите Ctrl+Enter.

    Python, разбирающий текстовый файл и логические методы

    Я немного увяз в логике Python.
    Я хотел бы получить несколько советов о том, как решить возникшую у меня проблему с python и методами анализа данных.

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

    Это необработанные данные

      ТП №: 1
    Частота: 12288,635 МГц
    Символьная скорость: 3000 KS
    Поляризация: Вертикальная
    Спектр: инвертированный
    Стандарт / модуляция: DVB-S2 / QPSK
    FEC: 1/2
    RollOff: 0.20
    Пилот: включен
    Режим кодирования: ACM / VCM
    Короткая рамка
    Транспортный поток
    Единый входной поток
    Уровень RF: -49 дБм
    Сигнал / шум: 6,3 дБ
    Ширина несущей: 3,600 МГц
    BitRate: 2,967 Мбит / с
      

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

      strings = («Частота», «Символ», «Полярный», «Мод», «FEC», «RF», «Сигнал», «Несущая», «Битрейт»)
    sat_raw = open ('/ BLScan / reports / 1520.txt ',' r ')
    sat_out = open ('1520out.txt', 'ш')
    для строки в sat_raw:
        если есть (s в строке для s в строках):
            на слово в line.split ():
                если ':' в слове:
                    sat_out.write (line.split (':') [- 1])
    sat_raw.close ()
    sat_out.close ()
      

    Выходные данные форматируются следующим образом перед отправкой в ​​базу данных

      12288,635 МГц
     3000 KS
     Вертикальный
     DVB-S2 / QPSK
     1/2
     -49 дБм
     6,3 дБ
     3,600 МГц
     2,967 Мбит / с
      

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

    1. Удалите десятичную запятую, 3 числа после нее и МГц в первой строке «частоты».
    2. Удалите все замыкающие эталоны измерений KS , дБм , дБ , МГц , Мбит .
    3. Объедините 9 полей в строку с разделителями-запятыми, чтобы каждый транспондер (примерно 30 на файл) находился в отдельной строке

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

    Анализ текста в формате Word из XML-документа в Azure Logic Apps

    Я пытаюсь проанализировать XML-файлы из библиотеки форм SharePoint, в которой пользователь скопировал / вставил форматированный текст документа Word в текстовое поле. В результате получается XML внутри XML. У меня были проблемы с получением содержимого, но с помощью другого вопроса этот синтаксис работал xpath (xml (output ('Get_file_content')? ['Body']), '// * [local-name () = "myFields"] // следующий-брат :: * [local-name () = "Request_Description"] ') [0] .Результат примерно такой

      
      
        
          
            
               Здесь идет описание запроса и желаемый результат 
    
          
        
    
    
      

    Как мне просто извлечь текст для описания? Мне интересно, нужно ли настроить мой первый оператор xpath , чтобы не отодвигать весь элемент.

    ОБНОВЛЕНИЕ

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

      
      
         тестировать случайную двойную кавычку внутри заголовка «здесь» тест и перенос  return 
    
    
      

    Это вызвано элементом управления RTF в форме, где пользователь может ввести текстовое поле в форме, и элемент управления преобразует это в XML, который вы видите.Поскольку нет согласованности, мне интересно, не является ли использование xpath жизнеспособным вариантом, но я не уверен, что еще можно сделать.

    нлп6

    нлп6

    Семантика — представление смысла в NLP

    Вся цель естественного языка — облегчить обмен идей среди людей о мире, в котором они живут. Эти идеи сходятся, чтобы сформировать «значение» высказывания или текста в форме серия предложений. Значение текста называется его семантикой . Полностью адекватная семантика естественного языка потребует полной теории о том, как люди думают и передают идеи. Поскольку такая теория не доступны сразу, исследователи естественного языка разработали больше скромный и прагматичный подход к семантике естественного языка.

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

    Логические и логические формы
    Значение простых предметов и событий
    Кванторы и значение определителей
    Значение модификаторов
    Прилагательные
    Относительные оговорки
    Множественное число, количество элементов и неисчисляемые существительные
    Отрицание
    Вопрос-ответ
    Да / Нет
    Сколько и какие
    Кто и что
    Референты дискурса, анафора, определенная ссылка (the)
    Устранение неоднозначности смысла слова
    Онтологические методы
    Статистические методы



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

    Рассмотрим предложение «Мяч красный». Его логическая форма может быть представлен красным (ball101). Эта же логическая форма одновременно представляет собой множество синтаксических выражений одной и той же идеи, например «Красный» это мяч.»и« Le bal est rouge ».

    Самая сложная проблема, которая стоит между синтаксическим высказыванием и его логическая форма — двусмысленность; лексическая неоднозначность, синтаксическая неоднозначность, и семантическая неоднозначность. Лексическая двусмысленность возникает со словами, которые иметь много чувств. Например, слово «идти» имеет как минимум следующее разные смыслы или значения:

    ход
    вылет
    чел.
    исчезнуть
    досягаемости
    продлить
    выставлен

    Усугубляет ситуацию то, что слово может иметь разные значения в разных части речи.Слово «мухи» как существительное имеет как минимум два значения. (насекомые, летающие шары) и еще как минимум два глагола (идет быстро, проходит воздух).

    Чтобы помочь с категоризацией и представлением значений слов, онтология часто используется. Онтология — это широкая классификация Схема для предметов, первоначально предложенная Аристотелем. Многие из оригинальные классификации используются и сегодня; вот типичный список:

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

    Грамматика падежа была предложена для дальнейшего прояснения отношений между действиями и объектами.Можно определить так называемые роли кейсов связывать определенные виды глаголов и различные объекты. Эти роли включают:

    агент
    тема
    инструмент

    Например, в фразе «Джон разбил окно молотком» грамматика падежа. идентифицирует Джона как агента, окно как тему, а молоток как инструмент. Дополнительные примеры ролей кейсов и их использования: дано у Аллена, стр. 248-9.

    Логические формы и лямбда-исчисление
    Язык логических форм — это комбинация предиката первого порядка исчисление (FOPC) и лямбда-исчисление.Исчисление предикатов включает унарные, бинарные и n-арные предикаты, такие как:

    Обозначение Lisp Обозначение пролога Представленный приговор
    (собака1 фидо1) dog1 (fido1) «Фидо — собака»
    (любит1 sue1 jack1) любит1 (sue1, jack1) «Сью любит Джека»
    (Broken1 John1 Window1 Hammer1) Broken1 (John1, Window1, Hammer1) «Джон разбил окно
    с молотком »

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

    Добавление числа к элементам в этих предикатах означает, что в семантическом представлении идеи мы говорим о конкретном экземпляр или интерпретация действия или объекта. Например, loves1 обозначает особое толкование слова «любовь».

    Логические формы могут быть построены из предикатов и других логических форм используя операторы & (и), => (подразумевает) и кванторы все и существует. В английском есть и другие полезные кванторы. помимо этих двух, таких как многие, несколько, большинство, и немного.Например, в предложении «Большинство собак лают» логическая форма

     (больше d1: (& (dog1 d1) (barks1 d1))) 
    Наконец, лямбда-исчисление полезно для семантического представления идеи естественного языка. Если p (x) — логическая форма, то выражение \ x.p (x) определяет функцию со связанной переменной Икс. Бета-редукция это формальное понятие применения функции к аргументу. Например, (\ x.p (x)) а применяет функцию \Икс.p (x) аргументу а, оставив р (а).
    Семантические правила для контекстно-свободных грамматик
    Один из способов создания семантических представлений для предложений — связать с каждым правилом грамматики связанный шаг, который определяет логическую форму что относится к каждой синтаксической категории. Рассмотрите простые грамматики с категориями S, NP, VP и TV.

    1. Если грамматическое правило S -> NP VP и логические формы для NP и VP являются NP ‘и VP’ соответственно, тогда логический форма S ‘для S есть VP’ (NP ‘).Например, в предложение «Бертран написал принципы» предположим, что

     NP '= bertrand и VP' = \ x.wrote (x, Principia) 
    Тогда логическая форма S ‘является результатом бета-редукции:
     (\ x.wrote (x, Principia)) bertrand = написал (bertrand, Principia) 
    2. Если грамматическое правило VP -> TV NP и логические формы для TV и NP — это TV ‘и NP’ соответственно, тогда логическое форма ВП ‘для ВП есть ТВ’ (НП ‘). Например, в фраза «написал принцип», если
     TV '= \ у.\ x.wrote (x, y) и NP '= Principia 
    Тогда логическая форма VP ‘= \ x.wrote (x, Principia) после Beta снижение.
    Представление пролога
    Чтобы учесть ограничения набора символов ASCII, следующие соглашения используются в Прологе для представления логических форм и лямбда-выражений.

    Выражение Соглашение пролога
    (для всех x: p (x)) все (X, p (X)) (напомним, что переменные Пролога пишутся с заглавной буквы)
    (существует x: p (x)) существует (X, p (X))
    и &
    подразумевает =>
    \ х.остановки (X), shrdlu, LF)

    дает ответ LF = останавливается (шрдлу).

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

    с (S) -> np (NP), vp (VP), {уменьшить (VP, NP, S)}.
    np (NP) -> det (D), n (N), {уменьшить (D, N, NP)}.
    нп (НП) -> п (НП).
    вп (ВП) -> тв (ТВ), НП (НП), {уменьшить (ТВ, НП, ВП)}.
    вп (ВП) -> IV (ВП).

    В первом правиле VP имеет лямбда-выражение, а NP — субъект.`halts (X)) -> [остановки].

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

    с (LF, [терри, написал, шрдлу], [])

    дает результат LF = написал (terry, shrdlu), в последовательности шагов, показанных следующей частичной трассировкой (неудачные шаги опускаются из этого следа):

    ? — s (LF, [terry, писал, shrdlu], []).
    Звонок: (7) s (_G276, [терри, написал, шрдлу], [])? слизняк
    Звонок: (8) np (_L131, [terry, написал, шрдлу], _L132)? слизняк
    Звоните: (9) n (_L131, [терри, написал, шрдлу], _L132)? слизняк
    Выход: (9) n (терри, [терри, написал, шрдлу], [написал, шрдлу])? слизняк
    Выход: (8) np (terry, [terry, написал, шрдлу], [написал, шрдлу])? слизняк
    Звоните: (8) vp (terry ^ _G276, [писал, шрдлу], [])? слизняк
    Звоните: (9) tv (_G382 ^ terry ^ _G276, [написал, шрдлу], _L171)? слизняк
    Выход: (9) tv (_G382 ^ terry ^ писал (terry, _G382), [писал, шрдлу], [шрдлу])? слизняк
    Звоните: (9) нп (_G382, [шрдлу], [])? слизняк
    Звоните: (10) п (_G382, [шрдлу], [])? слизняк
    Выход: (10) п (шрдлу, [шрдлу], [])? слизняк
    Выход: (9) нп (шрдлу, [шрдлу], [])? слизняк
    Выход: (8) vp (terry ^ написал (terry, шрдлу), [написал, шрдлу], [])? слизняк
    Выход: (7) s (написал (terry, shrdlu), [terry, написал, шрдлу], [])? слизняк

    LF = написал (terry, shrdlu)

    Количественные выражения существительных
    Логические формы, которые представляют количественные выражения существительных, такие как «каждая программа». в предложении «каждая программа останавливается» должны содержаться соответствующие количественные показатели.все (X, P => Q)) -> [каждые]. Логическая форма Пролога здесь является кодировкой лямбда-выражения \ p. \ Q. (All (x, p (x) => q (x))). Обратите внимание, что переменная x связана в каждом из выражения p и q.

    Это определение предполагает, что когда «каждый» используется в качестве определяющего в предложение, семантическое представление этого предложения — лямбда-выражение с двумя выражениями (P и Q), определяющими свойства объекта X, например что P (X) влечет Q (X).Например, в «каждая программа останавливается» выражение P => Q создается программой (X) и останавливается (X) для объекта X, поэтому мы имеем следующую логическую форму как значение представление:

    \ p. \ Q. (All (x, p (x) => q (x)) (\ y.halts (y)) (\ z.program (z))
    = \ q. (Все (x, программа (x) => д (х)) (\ y.halts (у)))
    = все (x, программа (x) => \ y.halts (y) (Икс) )
    = все (x, программа (x) => останавливается (x) )

    Вот частичная трассировка этого синтаксического анализа.все (_G382, `программа (_G382) => _ G389))

    Обратите внимание, что логическая форма, полученная в этом синтаксическом анализе, не совпадает с тот, который был разработан выше. Чтобы убедиться, что они эквивалентны, обратите внимание, что LF имеет вид `halts (\ z. \ X.all (x, program (x) => z (x))) и` halts имеет логическую форму \ y.halts (y).
    Бета уменьшив эти два, мы получим:

    (\ z. \ X.all (x, программа (x) => z (x)) (\ y.halts (y))
    = \ x.all (x, программа (x) => \ y.halts (y) (x))
    = \ х.S) -> whpron, sinv (S, пробел (np, X)).

    Упражнения
    1. Добавьте в лексикон соответствующую кодировку для определителя «а», чтобы его можно использовать в предложениях типа «Терри написал программу». След руки применение правил Пролога, приведенных в этом разделе, с этим предложением и показать промежуточные логические формы, которые приводят к его представлению в логической форме, существует (x, program (x) => написал (terry, X)).
    2. Предполагая грамматические правила, приведенные в этом разделе, найдите подходящий семантические представления для следующих утверждений:
    3. а.Терри написал программу, которая останавливает
      б. программа останавливается
      c. Терри написал каждую программу, которая останавливает
    4. Приведите пример вопроса «да-нет» и дополнительного вопроса, к которому могут применяться правила из последнего раздела. Для каждого примера покажите промежуточные этапы вывода логической формы вопроса. Предположим, что в лексиконе достаточно определений для общих слов, например, «кто», «сделал» и так далее.
    5. Посмотрите программу 4.2 на стр. 102 книги Перейра. Используя трассировку, покажите промежуточные шаги в синтаксическом разборе предложения «каждый студент написал программа «
    Список литературы
    Аллен, глава 9
    Перейра, глава 4




    «LEXSS» инъекция: как обойти лексические синтаксические анализаторы, злоупотребляя логикой синтаксического анализа HTML

    Адам Баннистер 24 июня 2021 г., 15:29 UTC
    Обновлено: 25 июня 2021 г. в 07:25 UTC

    Исследователь глубже исследует методику обнаружения недостатков в популярных текстовых редакторах WYSIWYG HTML

    Исследователь безопасности глубоко погрузился в обход лексических синтаксических анализаторов с помощью специальных тегов HTML, которые используют логику синтаксического анализа HTML для выполнения произвольного кода JavaScript.

    Крис Дэвис, консультант по безопасности в Bishop Fox, ранее применил метод взлома, чтобы выявить уязвимости межсайтового скриптинга (XSS) с высоким риском в двух популярных HTML-кодах What-You-See-Is-What-You-Get (WYSIWYG). текстовые редакторы.

    Недостатки в TinyMCE (раскрыты в августе 2020 г.) и Froala (раскрыты ранее в этом месяце) затронули в общей сложности 700 000 веб-сайтов, на которых были размещены эти приложения.

    Что такое лексический разбор?

    «Лексический синтаксический анализ — это очень сложный способ предотвращения XSS, потому что он оценивает, являются ли данные инструкциями или открытым текстом, перед выполнением дополнительной логики, такой как блокирование или кодирование данных», — говорит Дэвис в своей технической статье.

    Он отделяет «пользовательские данные (то есть неопасный текстовый контент) от компьютерных инструкций (то есть JavaScript и некоторых опасных HTML-тегов)», — продолжает он. «В тех случаях, когда пользователю разрешено использование подмножества HTML по замыслу, этот тип синтаксического анализа может использоваться для определения того, какой контент разрешен, а какой будет заблокирован или очищен».

    Наряду с редакторами WYSIWYG HTML, лексические анализаторы очистки широко используются для защиты редакторов форматированного текста, почтовых клиентов и библиотек очистки, таких как DOMPurify, от XSS-атак.

    Подробнее о новейших методах взлома

    Однако Дэвис демонстрирует, как лексические синтаксические анализаторы можно обманом заставить просматривать опасный контент «в виде текстовых данных, а не компьютерных инструкций».

    Это возможно, потому что «HTML не предназначен для двойного анализа; незначительные различия в синтаксическом анализе могут возникать между исходным синтаксическим анализатором HTML и очищающим синтаксическим анализатором; и дезинфекция парсеров часто реализуют собственную логику обработки ».

    Состояния контекста и путаница в пространстве имен

    Ключом к исследованию являются состояния контекста: категории состояний данных, по которым HTML-синтаксический анализатор сортирует элементы HTML во время токенизации.«Различные предоставленные элементы изменяют способ анализа и отображения данных в этих элементах, переключая состояние контекста данных», — сказал Дэвис.

    Метод исследователя «LEXSS» также использует путаницу с пространством имен, область исследований, которую в 2020 году впечатляюще развивает обходной путь DOMPurify Михала Бентковски. синтаксический анализатор, — сказал Дэвис.

    Концептуализация риска XSS

    Потенциальное влияние XSS-атак зависит от контекста.

    «Во многих случаях риск будет номинальным, а в других — катастрофическим», — сказал Крис Дэвис The Daily Swig. В самых серьезных случаях XSS может быть использован «для таких вещей, как перевод средств, выполнение сделок с финансовыми ценными бумагами или кража совершенно секретных данных».

    «Один из способов концептуализировать риск XSS — это подумать, когда вы находитесь на любом веб-сайте, что мог бы сделать злоумышленник, если бы он контролировал ваши действия? Поскольку XSS обеспечивает такой уровень контроля в пределах источника сайта, как правило, без ведома пользователя.«

    Предотвращение

    Что касается превентивных шагов,« при реализации приложений, которые допускают некоторый пользовательский HTML-код по дизайну », разработчики должны« обрабатывать HTML как можно ближе к исходному синтаксическому анализу », — объясняет Дэвис.

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

    Организациям также следует «рассмотреть возможность внедрения политики безопасности контента (CSP) в приложение», чтобы «заблокировать внедрение JavaScript на уровне, определяемом браузером».

    Будущие исследования

    На вопрос, почему он выбрал это направление исследований, Дэвис сказал The Daily Swig: «Этот тип анализа на основе анализа состояния контекста настолько широко распространен, но относительно не раскрыт.

    «Поэтому мне было интересно лучше понять, как разбирается HTML в целом и как редакторы стиля RTF или библиотеки очистки затем анализируют эти данные и как мы можем использовать эти знания».

    Он добавляет, что ожидает, что аналогичные недостатки обнаружатся в «некоторых действительно важных объектах», таких как почтовые клиенты, и что дальнейшее изучение HTML-синтаксического анализа также может быть плодотворным.

    «Я действительно надеюсь, что эта работа поможет другим исследователям вывести ее на новый уровень», — заключает он.

    РЕКОМЕНДУЕТСЯ Неправильная конфигурация в большинстве сред Active Directory создает серьезные бреши в безопасности, по мнению исследователей

    Анализ текста с использованием грамматики DSL

    Анализ текста с использованием грамматики DSL

    У нас есть задача разобрать следующий файл:

      6 шт яблоко
    12 шт апельсин
    3 шт персик
    имя: Джон Танадзаки
    адрес: проспект Фу, 5382
    Адрес 2 :
    почтовый индекс: 380802
    город: Кавагути-ши
    состояние: SA
    страна: ЯПОНИЯ
    Телефон :
    custmail: [электронная почта защищена]
    отправка: приоритет
    счет: 1544
    комментарий:
      

    Наша цель — преобразовать этот файл в формат JSON:

      {
      "Предметы": [
        {
          "item": "яблоко",
          «количество»: 6
        },
        {
          "item": "оранжевый",
          «количество»: 12
        },
        {
          "item": "персик",
          «количество»: 3
        }
      ],
      "custname": "Джон Танадзаки",
      «адрес»: «проспект Фу, 5382», г.
      "Адрес 2": "",
      "zip": "380802",
      "город": "Кавагути-ши",
      "состояние": "SA",
      "страна": "ЯПОНИЯ",
      "Телефон": "",
      "custmail": "[электронная почта защищена]",
      "shipby": "приоритет",
      "invoice": "1544",
      "комментарий": ""
    }
      

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

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

    Что это за грамматика, о которой мы говорим?

    Мы будем использовать так называемый вариант формы Бэкуса-Наура (BNF) для описания контекстно-свободной грамматики. Оказывается, BNF используется везде, например. GNU-Bison основан на BNF и используется во многих современных компиляторах.

    Этапы синтаксического анализа контекстно-свободной грамматики

    Конвейер синтаксического анализа состоит из трех основных этапов. Первый шаг — формально провести лексический анализ. Лексический анализ — это процесс определения типов кубиков лего, так сказать, которые мы будем использовать для построения грамматики.Формально они называются «токенами».

    Knolling, Источник: https://www.reddit.com/r/knolling/comments/6ivd68/almost_all_of_the_603_pieces_of_the_lego_technic/

    Семантический анализ — это процесс построения абстрактного синтаксического дерева или AST. AST — это представление того, из чего состоит окончательная сборка лего. В корне будет последняя сборка, а листья — это каждая из «выпуклых» частей лего.

    И последняя часть — сборка. Это процесс обхода AST и выполнения каких-либо действий с каждым узлом.В случае компиляторов каждый узел будет представлять операцию узла, инструкцию на языке ассемблера. \ S \ r \ n] Значение (любой текст) значение .* Ключ ключ 'custname' / 'address2' / 'address' / 'zip' / 'city' / state '/' country '/' phone '/' custmail '/' shipby '/' comment '/' invoice '/ 'печатный' Разделитель ключей / значений толстая кишка : Кол-во количество \ г +

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

    Описание токена Имя токена Значения токенов
    Запись запись ключ ws двоеточие значение ws новая строка
    Арт. товар количество ws "шт" ws значение новая строка
    Товаров предметы товар +
    Файл файл (новая строка / элементы / запись) +

    Здесь / означает оператор ИЛИ .Остальной синтаксис — стандартные регулярные выражения.

    Экономичная библиотека для Python (EBNF Parser)

    Для построения грамматики DSL мы можем использовать Parsimonious, библиотеку Python, основанную на грамматике синтаксического анализа (PEGs). Цитата из документов:

    синтаксические анализаторы PEG не проводят различия между лексированием и синтаксическим анализом; все делается сразу. В результате отсутствует ограничение на просмотр вперед, как, например, с Yacc. И благодаря обоим этим свойствам грамматики PEG легче писать: они, по сути, просто более практичный диалект EBNF.При кэшировании они занимают память O (размер грамматики * длина текста) (хотя я планирую сделать лучше), но они работают за время O (длина текста).

    PEG могут описывать расширенный набор из LL (k), языков, любой детерминированный язык LR (k) и многие другие, включая те, которые не являются контекстно-независимыми (http://www.brynosaurus.com/pub /lang/peg.pdf). Они также могут иметь дело с языками, которые были бы неоднозначными, если бы они были описаны в каноническом EBNF. Они делают это, продавая | Оператор чередования для оператора /, который работает так же, за исключением того, что делает приоритет явным: a / b / c сначала пытается сопоставить a .Если это не удается, он пробует b и, если это не удается, переходит к c . Таким образом, двусмысленность разрешается путем всегда первого успешного распознавания.

    Синтаксис для построения токенов основан на упрощенном расширенном формате Бэкуса-Наура (EBNF). Синтаксис токенов подробно описан в README.

    Абстрактное синтаксическое дерево (AST)

    Связь токенов можно описать как AST, начиная с файла в качестве корневого узла:

    Абстрактное синтаксическое дерево порядка (AST)

    Все узлы разбиты на фундаментальные строительные блоки.Я добавил в файл узел новой строки , так как в конце файла могут быть пустые строки. Этот узел вернет None , как мы увидим в следующем разделе. Дерево может быть представлено с использованием синтаксиса Parsimonious в Python следующим образом:

      из parsimonious.grammar import Grammar
    
    грамматика = Грамматика (
        р"""
        файл = (новая строка / элементы / запись) +
        items = item +
        item = количество ws "шт" ws значение новая строка
        количество = ~ "\ d +"
        запись = ключ ws двоеточие ws значение новая строка
        двоеточие = ~ ":"
        key = 'custname' / 'address2' / 'address' / 'zip' / 'city' / 'state' / 'country' / 'phone' / 'custmail' / 'shipby' / 'comment' / 'invoice' / 'напечатанный'
        значение = ~ ".\ S \ r \ n] * "
        новая строка = ~ "[\ r \ n] +"
        "" "
    )
      

    Assembly

    Мы можем использовать скупую библиотеку для «сборки» узлов и создания объекта JSON. Сначала мы создаем class call Order , который наследуется от NodeVisitor Parsimonious class. Каждый узел в дереве получает метод с префиксом visit_ . Например, для элемента узла нам нужно определить метод visit_item .

    Полный класс Order определяется следующим образом:

      из экономичного импорта NodeVisitor
    
    Порядок класса (NodeVisitor):
    
        def visit_file (сам, узел, посещенные_дети):
            "" "Собирает элементы и записи для файла" ""
            результат = {'предметы': []}
            для i в visit_children:
                если i [0] равно None:
                    Продолжить
                пытаться:
                    результат.обновить (dict (i))
                кроме исключения как e:
                    печать (я)
                    печать (е)
                    проходить
    
            вернуть результат
    
        def visit_newline (я, узел, посещенные дети):
            "" "Узел новой строки ничего не делает и не возвращает None" ""
            return None
    
        def visit_items (я, узел, посещенные дети):
            "" "Создает словарь для каждого элемента из элементов" ""
            вернуть 'items', [{'item': p, 'amount': q} для p, q в посещенных_детях]
    
        def visit_item (я, узел, посещенные дети):
            "" "Создает кортеж элемента, количество" ""
            количество, _, _, _, продукт, _ = посещенные_дети
            print (f'ITEM: {product.текст}: {количество.text} ')
            вернуть product.text, int (количество.text)
    
        def visit_record (сам, узел, посещенные дети):
            "" "Собирает запись (кортеж) ключа, значений" ""
            ключ, _, _, _, значение, * _ = node.children
            print (f'RECORD: {key.text}: {value.text} ')
            вернуть key.text, value.text
    
        def visit_value (я, узел, посещенные_дети):
            "" "Возвращает значение как есть" ""
            возвратный узел
    
        def generic_visit (я, узел, посещенные_дети):
            "" "Возвращает все" ""
            вернуть visit_children или узел
      

    Теперь мы можем преобразовать в JSON :

      import json
    
    order = Заказ ()
    дерево = грамматика.совпадение (данные)
    output = order.visit (дерево)
    output_json = json.dumps (вывод)
    печать (output_json)
      

    , который печатает наш объект заказа JSON :

      {
      "Предметы": [
        {
          "item": "яблоко",
          «количество»: 6
        },
        {
          "item": "оранжевый",
          «количество»: 12
        },
        {
          "item": "персик",
          «количество»: 3
        }
      ],
      "custname": "Джон Танадзаки",
      «адрес»: «проспект Фу, 5382», г.
      "Адрес 2": "",
      "zip": "380802",
      "город": "Кавагути-ши",
      "состояние": "SA",
      "страна": "ЯПОНИЯ",
      "Телефон": "",
      "custmail": "[электронная почта защищена]",
      "shipby": "приоритет",
      "invoice": "1544",
      "комментарий": ""
    }
      

    Почему не построчный синтаксический анализ на основе правил?

    Надо задаться вопросом - зачем писать грамматику, если мы можем просто поместить некоторые правила в цикл for, которые могут перемещаться по файлу построчно?

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

      фруктов:
        6 шт яблоко
        12 шт апельсин
        3 шт персик
    овощи:
        Лук репчатый 3шт
        5 шт морковь
    имя: Джон Танадзаки
    адрес: проспект Фу, 5382
    Адрес 2 :
    почтовый индекс: 380802
    город: Кавагути-ши
    состояние: SA
    страна: ЯПОНИЯ
    Телефон :
    custmail: [электронная почта защищена]
    отправка: приоритет
    счет: 1544
    комментарий:
      

    , который должен разбираться как:

      {
      "Предметы": {
        "фрукты": [
          {
            "item": "яблоко",
            «количество»: 6
          },
          {
            "item": "оранжевый",
            «количество»: 12
          },
          {
            "item": "персик",
            «количество»: 3
          }
        ],
        "овощи": [
          {
            "item": "лук",
            «количество»: 3
          },
          {
            "item": "морковь",
            «количество»: 5
          }
        ]
      },
      "custname": "Джон Танадзаки",
      «адрес»: «проспект Фу, 5382», г.
      "Адрес 2": "",
      "zip": "380802",
      "город": "Кавагути-ши",
      "состояние": "SA",
      "страна": "ЯПОНИЯ",
      "Телефон": "",
      "custmail": "[электронная почта защищена]",
      "shipby": "приоритет",
      "invoice": "1544",
      "комментарий": ""
    }
      

    Исходный AST легко изменить с помощью следующей грамматики:

      грамматика = Грамматика (
        р"""
        файл = (новая строка / категории / элементы / запись) +
        категории = категория +
        category = key_cat ws двоеточие ws элементы новой строки
        items = ws item +
        item = ws количество ws "шт" ws значение новая строка
        количество = ~ "\ d +"
        запись = ключ ws двоеточие ws значение новая строка
        двоеточие = ~ ":"
        key_cat = 'фрукты' / 'овощи'
        key = 'custname' / 'address2' / 'address' / 'zip' / 'city' / 'state' / 'country' / 'phone' / 'custmail' / 'shipby' / 'comment' / 'invoice' / 'напечатанный'
        значение = ~ ".\ S \ r \ n] * "
        новая строка = ~ "[\ r \ n] +"
        "" "
    )
      

    И измените класс Order как:

      class Order (NodeVisitor):
    
        def visit_file (сам, узел, посещенные_дети):
            "" "Собирает элементы и записи для файла" ""
            "" "Возвращает файл" ""
            результат = {}
            для i в visit_children:
                если i [0] равно None:
                    Продолжить
                пытаться:
                    result.update (dict (i))
                кроме исключения как e:
                    печать (я)
                    печать (е)
                    проходить
    
            вернуть результат
    
    
        def visit_newline (я, узел, посещенные дети):
            "" "Узел новой строки ничего не делает и не возвращает None" ""
            return None
    
    
        def visit_categories (я, узел, посещенные дети):
            "" "Создает кортеж элементов и словарь категорий" ""
            вернуть 'items', dict (посещенные_дети)
    
    
        def visit_category (я, узел, посещенные дети):
        "" "Создает кортеж категории и список элементов" ""
            ключ, _, _, _, _, items = посещенные_дети
            print (f'CATEGORY: {ключ [0].text}: {items [1]} ')
            ключ возврата [0]. текст, элементы [1]
    
    
        def visit_items (я, узел, посещенные дети):
            "" "Создает словарь для каждого элемента из элементов" ""
            вернуть 'items', [{'item': p, 'amount': q} для p, q в посещенных_детях [1]]
    
    
        def visit_item (я, узел, посещенные дети):
            "" "Создает кортеж элемента, количество" ""
            _, количество, _, _, _, продукт, _ = посещенные_дети
            print (f'ITEM: {product.text}: {quantity.text} ')
            вернуть товар.текст, int (количество.текст)
    
    
        def visit_record (сам, узел, посещенные дети):
            "" "Собирает запись (кортеж) ключа, значений" ""
            ключ, _, _, _, значение, * _ = node.children
            print (f'RECORD: {key.text}: {value.text} ')
            вернуть key.text, value.text
    
    
        def visit_value (я, узел, посещенные_дети):
            "" "Возвращает значение как есть" ""
            возвратный узел
    
    
        def generic_visit (я, узел, посещенные_дети):
            "" "Возвращает все" ""
            вернуть visit_children или узел
    
      

    Его можно анализировать построчно, но он будет трудоемким, ошибочным и невероятно хрупким в обслуживании и отладке.Лексический анализ и семантический синтаксический анализ - это очень мощный инструмент, который можно иметь в кармане, когда возникает необходимость в синтаксическом анализе файлов журналов странного формата, текстовых блобов или даже в создании собственного имитационного компилятора для учебных целей.

    Конец.

    Построение парсеров с рекурсивным спуском: полное руководство

    Синтаксические анализаторы - это программы, помогающие обрабатывать текст. Компиляторы и интерпретаторы используют синтаксические анализаторы для анализа программ перед их дальнейшей обработкой, а синтаксические анализаторы для таких форматов, как JSON и YAML, преобразуют текст в собственные структуры данных языка программирования.

    В этой статье мы рассмотрим, как создавать «синтаксические анализаторы рекурсивного спуска». Синтаксические анализаторы с рекурсивным спуском - это простой, но мощный способ создания синтаксических анализаторов: для каждой «сущности» в тексте, который вы хотите обработать, вы определяете функцию.

    Сначала мы рассмотрим некоторые теории, лежащие в основе синтаксического анализа. Затем, используя Python, мы создадим калькулятор и парсер JSON, который поддерживает комментарии, запятые и строки без кавычек. Наконец, мы собираемся обсудить, как вы можете создавать синтаксические анализаторы для других языков, которые ведут себя иначе, чем приведенные выше примеры.

    Если вы уже знакомы с теорией, вы можете сразу перейти к тем частям, в которых мы создаем синтаксический анализатор.

    Как работает парсинг?

    Для обработки фрагмента текста программа выполняет три задачи. В процессе, называемом «лексированием» или «токенизацией», программа перебирает символы в тексте и извлекает логические группы, называемые «токенами». Например, в таком выражении, как «3 + 5-10», программа для обработки арифметических выражений может извлекать следующие токены:

     [{"Тип": "Число", "Текст": "3"},
     {"Тип": "Оператор", "Текст": "+"},
     {"Тип": "Число", "Текст": "5"},
     {"Тип": "Оператор", "Текст": "-"},
     {"Тип": "Число", "Текст": "10"}] 

    Затем программа использует правила для определения значимых последовательностей токенов, и это называется «синтаксическим анализом».Правила, используемые для этого, называются «грамматиками» - примерно так же, как слова естественного языка, такого как английский, соединяются в значимые последовательности с использованием английской грамматики. Индивидуальные правила грамматики называются «постановками».

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

    Значок указывает, что группа может повторяться любое количество раз (включая ноль).

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

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

    Письменные правила производства

    В оставшейся части статьи мы будем использовать производственные правила для визуализации грамматик.Поэтому в этой статье мы объяснили, как мы пишем производственные правила. Если вы ранее читали учебник по синтаксическому анализу, обозначения, которые мы использовали здесь, немного отличаются.

    Ранее мы видели пример простого производства. Когда правило грамматики содержит имя токена, а токен довольно простой, принято писать текст токена в грамматике. Если мы рассмотрим предыдущий пример, мы получим + или - , поэтому мы могли бы переписать правило так:

    Мы также видели, как мы можем использовать, чтобы указать, что что-то повторяется любое количество раз.Точно так же a указывает на то, что что-то повторяется, но это должно произойти хотя бы один раз. A указывает на то, что не является обязательным.

    В качестве примера предположим, что мы создаем синтаксический анализатор для обработки списка условий, например «x> 10 и y> 30». Предположим, что и не являются обязательными, поэтому мы можем переписать это условие как «x> 10 y> 30». Вы можете разработать грамматику для вышеперечисленного так:

    Когда есть несколько альтернатив, порядок альтернатив имеет значение.Для такого правила, как, мы должны сначала попытаться сопоставить «+», а затем «-». Хотя в этом тривиальном примере может показаться, что порядок не имеет значения, позже мы увидим примеры, когда порядок становится важным.

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

    Соображения при создании синтаксических анализаторов

    Ранее мы видели несколько простых грамматик. Однако при попытке разобрать что-то более сложное может возникнуть ряд проблем.Мы собираемся их здесь обсудить.

    Обработка приоритета в грамматиках

    Ранее мы видели грамматику, которая может обрабатывать математические выражения, состоящие из сложений и вычитаний. К сожалению, вы не можете расширить ту же грамматику для умножения (и деления), поскольку эти операции имеют более высокий приоритет, чем сложение (и вычитание).

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

    Давайте возьмем выражение (например, «3 + 2 * 5»), чтобы убедиться, что это действительно работает. Предположим, что как только наш синтаксический анализатор завершит сопоставление с правилом грамматики, он автоматически вычислит выражение. Мы также будем использовать подчеркнутый текст, чтобы указать, что ищет синтаксический анализатор. Кроме того, для ясности мы опустили цитаты.

    Когда анализируется 2 * 5 , синтаксический анализатор немедленно возвращает результат 10.Этот результат получается на шаге, и в конце выполняется сложение, в результате чего получается 13.

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

    Избегание левой рекурсии

    Ранее мы упоминали, что парсер рекурсивного спуска состоит из функций, которые обрабатывают «сущности» в тексте. Теперь, когда мы знаем о токенах и производственных правилах, давайте переопределим это немного более формально: каждая функция в парсере извлекает токен или реализует логику производственного правила.

    Давайте возьмем простую грамматику, например, чтобы увидеть, что это на самом деле означает. Если бы вы написали псевдокод для этой грамматики, он выглядел бы примерно так:

     def выражение ():
        first_number = число ()
        читать ('+')
        второе_число = число ()
        # обрабатываем эти два числа, например добавляя их
     

    Затем рассмотрим грамматику, которая анализирует список чисел, разделенных запятыми. Обычно вы пишете это так:

    А теперь представьте, что по какой-то причине вы хотели избежать подстановочного знака и переписали его так:

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

    Однако у этого подхода есть проблема - вы вводите бесконечную рекурсию! Если вы попытаетесь вызвать функцию list () один раз, вы вызовете ее снова и так далее. Вы можете подумать, что переписывание грамматики может помочь. Однако, если вы попытаетесь написать функцию, вы прочитаете одно число и прекратите синтаксический анализ. Число, которое вы читаете, может быть частью списка, такого как «2, 4, 6», и вы не сможете прочитать другие числа.

    При синтаксическом анализе это называется левой рекурсией. Случай, который мы видели выше, включает «прямую левую рекурсию», но есть также «косвенная левая рекурсия», когда A вызывает B, B вызывает A и так далее. В любом случае вам следует переписать грамматику другим способом, чтобы избежать этой проблемы.

    Как избежать возврата

    Предположим, вы пытаетесь создать синтаксический анализатор, который анализирует операцию с двумя числами, например «2 + 4», и записывает грамматику таким образом:

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

    Чтобы понять, почему это так, рассмотрим входную строку «5 - 2». Сначала мы воспользуемся правилом сложения и проанализируем число. Тогда мы ожидаем «+», но при чтении строки мы получим «-», что означает, что нам нужно вернуться назад и применить правило вычитания. При этом мы потратили время и циклы ЦП на синтаксический анализ числа только для того, чтобы отбросить его и снова проанализировать как часть правила вычитания.

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

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

    Создание базы для синтаксического анализатора рекурсивного спуска на Python

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

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

    Класс исключения

    Поскольку это самая простая задача, мы займемся ею в первую очередь.Мы определим наш собственный класс исключения с именем ParseError следующим образом:

     класс ParseError (Исключение):
        def __init __ (self, pos, msg, * args):
            self.pos = pos
            self.msg = msg
            self.args = args
    
        def __str __ (сам):
            вернуть '% s в позиции% s'% (self.msg% self.args, self.pos)
     

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

     e = ParseError (13, 'Ожидается "{", но найдено "% s"', "[") 

    Когда вы попытаетесь распечатать исключение, вы получите отформатированное сообщение вроде этого:

     Ожидается "{", но найдено "[" в позиции 13 

    Обратите внимание, что мы не использовали символ % в строке формата и ее аргументах (например, '"Ожидается" {", но нашли"% s "'%" [").Это обрабатывается в методе __str__ класса, где мы отформатировали self.msg с элементами в self.pos .

    Теперь вы можете спросить, почему мы хотим отформатировать его в методе __str__ вместо того, чтобы записывать его с помощью % ? Оказывается, использование оператора % для форматирования строки занимает довольно много времени. При синтаксическом анализе строки возникает множество исключений, поскольку синтаксический анализатор пытается применить различные правила.Следовательно, мы отложили форматирование строки до того момента, когда оно действительно необходимо.

    Базовый класс парсера

    Теперь, когда у нас есть класс ошибок, следующим шагом будет написание класса синтаксического анализатора. Определим его так:

    Парсер класса
    :
        def __init __ (сам):
            self.cache = {}
    
        def parse (self, text):
            self.text = текст
            self.pos = -1
            self.len = len (текст) - 1
            rv = self.start ()
            self.assert_end ()
            возврат RV 

    Здесь вы заметите элемент cache в методе __init__ - мы вернемся к тому, зачем это нужно, когда будем обсуждать распознавание символов.

    Метод parse довольно прост - во-первых, он сохраняет строку. Поскольку мы еще не сканировали ни одну часть строки, мы установим позицию на -1. Кроме того, мы сохраним максимальный индекс строки в self.len . (Максимальный индекс всегда на единицу меньше длины строки.)

    Затем мы начнем анализ строки и проверим, дошли ли мы до конца строки. Это сделано для того, чтобы наш синтаксический анализатор смог обработать весь текст, ничего не оставив в конце.После этого возвращаем результат.

    Метод assert_end прост: мы проверим, меньше ли текущая позиция максимального индекса. Имейте в виду, что эти методы находятся внутри класса, хотя для простоты мы опускаем отступ.

     def assert_end (сам):
        если self.pos 
     

    На протяжении всего синтаксического анализатора мы будем смотреть на символ, который на единицу больше текущего индекса ( self.поз ). Чтобы понять, почему это так, представьте, что мы начинаем с self.pos = -1 , поэтому мы будем смотреть на индекс 0. После успешного распознавания символа self.pos переходит к 0, и мы будем смотреть на символ с индексом 1, и так далее. Вот почему в ошибке используется self.pos + 1 .

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

     def eat_whitespace (сам):
        в то время как self.pos 
      

    Обработка диапазонов символов

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

     сам.char ('A-Za-z_')
     

    Мы дадим метод char , содержащий список символов или диапазонов (например, A – Z в приведенном выше примере). Диапазоны обозначаются двумя символами, разделенными от до . У первого символа числовое значение меньше, чем у правого.

    Теперь, если символ в self.text [self.pos + 1] совпадает с чем-то в аргументе self.char , мы его вернем. В противном случае мы вызовем исключение.

    Внутренне мы преобразуем строку в нечто более простое для обработки - список с отдельными символами или диапазонами, например ['A-Z', 'a-z', '_'] . Итак, напишем функцию для разделения диапазонов:

     def split_char_ranges (self, chars):
        пытаться:
            вернуть self.cache [символы]
        кроме KeyError:
            проходить
    
        rv = []
        индекс = 0
        длина = len (символы)
    
        пока индекс <длина:
            если index + 2  = символы [индекс + 2]:
                    Raise ValueError ('Плохой диапазон символов')
    
                rv.добавить (символы [индекс: индекс + 3])
                индекс + = 3
            еще:
                rv.append (символы [индекс])
                индекс + = 1
    
        self.cache [chars] = rv
        возврат RV 

    Здесь мы перебираем строку chars в поисках , за которыми следует другой символ. Если это условие совпадает и первый символ меньше последнего, мы добавляем весь диапазон (например, A – Z ) в список rv . В противном случае мы добавляем символ chars [index] к rv .

    Затем мы добавляем список rv в кеш. Если мы увидим эту строку во второй раз, мы вернем ее из кеша с помощью try ... за исключением блока KeyError: ... вверху.

    По общему признанию, мы могли бы просто предоставить список типа ['A-Z', 'a-z', '_'] для метода char . Однако, на наш взгляд, такой способ делает вызовы char () немного чище.

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

     def char (self, chars = None):
        если self.pos> = self.len:
            поднять ParseError (
                self.pos + 1,
                'Ожидается% s, но есть конец строки',
                'character', если chars - None else '[% s]'% chars
            )
    
        next_char = self.text [self.pos + 1]
        если символы == Нет:
            self.pos + = 1
            вернуть next_char
    
        для char_range в self.split_char_ranges (chars):
            если len (char_range) == 1:
                если next_char == char_range:
                    себя.поз + = 1
                    вернуть next_char
            elif char_range [0] <= next_char <= char_range [2]:
                self.pos + = 1
                вернуть next_char
    
        поднять ParseError (
            self.pos + 1,
            'Ожидается% s, но есть% s',
            'character', если chars - None else '[% s]'% chars,
            next_char
        )
     

    Давайте сначала сосредоточимся на аргументе chars = None . Это позволяет вызывать self.char () без указания набора символов. Это полезно, когда вы хотите просто извлечь символ, не ограничивая его определенным набором.В противном случае вы можете вызвать его с помощью ряда символов, как мы видели в предыдущем разделе.

    Эта функция довольно проста - сначала мы проверяем, не закончился ли текст. Затем мы выбираем следующий символ и проверяем, является ли chars None , и в этом случае мы можем увеличить позицию и немедленно вернуть символ.

    Однако, если есть набор символов в chars , мы разбиваем его на список отдельных символов и диапазонов, например ['A-Z', 'a-z', '_'] .В этом списке строка длиной 1 является символом, а все остальное - диапазоном. Он проверяет, соответствует ли следующий символ этому символу или диапазону, и если это так, мы возвращаем его. Если нам не удалось сопоставить его с чем-либо, он выходит из цикла, который вызывает ParseError , заявляя, что мы не можем получить ожидаемый символ.

    'символ' , если символы не являются другими '[% s]'% символов - это просто способ предоставления более удобочитаемого исключения. Если символов равно Нет , сообщение об исключении будет читать Ожидаемый символ, но получено... , но если для char было задано что-то вроде A-Za-z_ , мы получили бы Expected [A-Za-z_], но получили бы ... .

    Позже мы увидим, как использовать эту функцию для распознавания токенов.

    Извлечение ключевых слов и символов

    Помимо извлечения отдельных символов, распознание ключевых слов является общей задачей при создании синтаксического анализатора. Мы используем «ключевые слова» для обозначения любой непрерывной строки, которая является «собственной сущностью», и перед ней и после нее могут быть пробелы.Например, в JSON { может быть ключевым словом, а в языке программирования , если , , иначе может быть ключевым словом и т. Д.

    Это код для распознавания ключевых слов:

     def ключевое слово (self, * ключевые слова):
        self.eat_whitespace ()
        если self.pos> = self.len:
            поднять ParseError (
                self.pos + 1,
                'Ожидается% s, но есть конец строки',
                ','. join (ключевые слова)
            )
    
        для ключевого слова в ключевых словах:
            низкий = сам.позиция + 1
            high = low + len (ключевое слово)
    
            if self.text [low: high] == ключевое слово:
                self.pos + = len (ключевое слово)
                self.eat_whitespace ()
                вернуть ключевое слово
    
        поднять ParseError (
            self.pos + 1,
            'Ожидается% s, но есть% s',
            ','. join (ключевые слова),
            self.text [self.pos + 1],
        )
     

    Этот метод принимает ключевые слова, такие как self.keyword ('if', 'and', 'or') . Метод удаляет пробелы, а затем проверяет, не закончился ли текст для синтаксического анализа.Затем он выполняет итерацию по каждому ключевому слову, проверяя, существует ли ключевое слово в тексте. Если он что-то найдет, мы удалим пробелы после ключевого слова, а затем вернем ключевое слово.

    Однако, если он чего-то не находит, он выходит из цикла, который вызывает ParseError , заявляя, что ключевые слова не могут быть найдены.

    Помощник для сопоставления нескольких производств

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

    Мы также предполагаем, что текст содержит слово. Когда синтаксический анализатор пытается найти цифру (возможно, используя char () ), он не находит ее, и это вызывает исключение. Кроме того, вы должны удалить пробелы до и после соответствия производственному правилу. Итак, код для item () может выглядеть так:

     def item (self):
    себя.eat_whitespace ()
    пытаться:
    rv = self.number ()
    кроме ParseError:
    rv = self.word ()
    self.eat_whitespace ()
    вернуть фургон
     

    Похоже, это уже много для реализации простого правила! Представьте, каково было бы реализовать сложное правило - вам нужно будет использовать try ... кроме блоков повсюду.

    Итак, мы напишем функцию match () , которая удалит пробелы и попытается сопоставить несколько правил. Функция следующая:

     def match (self, * rules):
        себя.eat_whitespace ()
        last_error_pos = -1
        last_exception = Нет
        last_error_rules = []
    
        для правила в правилах:
            initial_pos = self.pos
            пытаться:
                rv = getattr (сам, правило) ()
                self.eat_whitespace ()
                вернуть фургон
            кроме ParseError как e:
                self.pos = initial_pos
    
                если e.pos> last_error_pos:
                    last_exception = e
                    last_error_pos = e.pos
                    last_error_rules.clear ()
                    last_error_rules.добавить (правило)
                elif e.pos == last_error_pos:
                    last_error_rules.append (правило)
    
        если len (last_error_rules) == 1:
            поднять last_exception
        еще:
            поднять ParseError (
                last_error_pos,
                'Ожидается% s, но есть% s',
                ','. join (last_error_rules),
                self.text [last_error_pos]
            )
     

    Прежде чем обсуждать, как это работает, давайте посмотрим, насколько просто переписать наш предыдущий пример, используя match () :

     def item (self):
        вернуть себя.совпадение ('число', 'слово')
     

    Итак, как это работает? match () принимает имя метода для запуска (например, номер или слово в приведенном выше примере). Во-первых, он избавляется от пробелов в начале. Затем он перебирает все имена методов и выбирает каждый метод, используя его имя через getattr () . Затем он пытается вызвать метод и, если все идет хорошо, также удаляет пробелы после текста. Наконец, он возвращает значение, полученное при вызове метода

    .

    Однако, если возникает ошибка, он сбрасывает самостоятельно.pos к значению, которое было до попытки сопоставления с правилом. Затем он пытается выбрать хорошее сообщение об ошибке, используя следующие правила:

    • При сопоставлении нескольких правил многие из них могут вызывать ошибку. Ошибка, возникшая после синтаксического анализа большей части текста, вероятно, является тем сообщением об ошибке, которое нам нужно.

    Чтобы понять, почему это так, рассмотрим строку «abc1». Попытка вызвать number () завершится ошибкой в ​​позиции 0, тогда как word () завершится ошибкой в ​​позиции 2.Глядя на строку, вполне вероятно, что пользователь хотел ввести слово, но допустил опечатку.

    • Если два или более ошибочных правила заканчиваются «ничьей», мы предпочитаем сообщить пользователю обо всех правилах, которые потерпели неудачу.

    last_error_pos и last_exception отслеживают позицию и исключение последней ошибки соответственно. Кроме того, мы ведем список last_error_rules , чтобы в случае ничьей мы могли сообщить пользователю обо всех правилах, которые мы пытались сопоставить.

    В блоке , кроме ParseError , мы сравниваем позицию последней ошибки и текущую ошибку. Если текущая ошибка совпадает, мы считаем это ничьей и добавляем в список. В противном случае мы обновляем позицию ошибки, исключение и список ошибочных правил.

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

    Что дальше? Вспомогательные функции

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

    Для этого мы представим трех маленьких помощников. В случае исключения при поиске материала они вернут Нет :

     def might_char (self, chars = None):
        пытаться:
            вернуть себя.char (символы)
        кроме ParseError:
            return None
    
    def might_match (self, * правила):
        пытаться:
            вернуть self.match (* правила)
        кроме ParseError:
            return None
    
    def might_keyword (self, * ключевые слова):
        пытаться:
            вернуть self.keyword (* ключевые слова)
        кроме ParseError:
            return None
     

    Использовать эти функции очень просто. Вот как их можно использовать:

     оператор = self.maybe_keyword ('+', '-'):
    если оператор == '+':
        # складываем два числа
    elif operator == '-':
        # вычесть два числа
    else: # оператор None
        # сделай что-нибудь еще
     

    Первый пример парсера: калькулятор

    Теперь, когда мы создали основу для написания синтаксического анализатора, мы создадим наш первый синтаксический анализатор.Он сможет анализировать математические выражения с помощью сложения, вычитания, умножения, деления, а также обрабатывать такие выражения в скобках, как «(2 + 3) * 5».

    Начнем с визуализации грамматики в виде производственных правил.

    Правила изготовления калькулятора грамматики

    Ранее мы видели грамматику для обработки всего, кроме выражений в скобках:

    Теперь давайте подумаем, как выражения в скобках вписываются в эту грамматику.При вычислении «(2 + 3) * 5» нам нужно будет вычислить «(2 + 3)» и уменьшить его до числа. Это означает, что в приведенной выше грамматике термин «Число» может относиться либо к выражению в скобках, либо к чему-то, что на самом деле является числом, например «5».

    Итак, мы изменим наши правила, чтобы учесть и то, и другое. В правиле «Срок» мы заменим «Число» на более подходящий термин, например «Фактор». Тогда «Фактор» может относиться либо к выражению в скобках, либо к «Числу». Модифицированная грамматика выглядит так:

    Разобравшись с этим, давайте реализуем производственные правила!

    Реализация парсера

    Мы реализуем наш анализатор Calculator, расширив базовый класс Parser.Мы начинаем определение класса так:

     класс CalcParser (Парсер):
        def start (self):
            вернуть self.expression ()
     

    Раньше при реализации базового класса синтаксического анализатора у нас был метод start () для запуска синтаксического анализа. Мы просто вызовем здесь метод expression () , который мы определим следующим образом:

     def выражение (self):
        rv = self.match ('термин')
        в то время как True:
            op = self.maybe_keyword ('+', '-')
            если op равно None:
                перерыв
    
            термин = сам.совпадение ('термин')
            если op == '+':
                rv + = срок
            еще:
                rv - = срок
    
        вернуть фургон
     

    Грамматическое правило для. Итак, начнем с чтения термина из текста. Затем мы устанавливаем бесконечный цикл и ищем «+» или «-». Если мы не найдем ни того, ни другого, это означает, что мы закончили читать список дополнительных терминов, предоставленный, так что мы можем выйти из цикла.

    Иначе читаем другой термин из текста. Затем, в зависимости от оператора, мы либо добавляем, либо вычитаем его из начального значения.Мы продолжаем этот процесс до тех пор, пока не получим «+» или «-» и не вернем значение в конце.

    Мы реализуем term () таким же образом:

     def term (self):
        rv = self.match ('коэффициент')
        в то время как True:
            op = self.maybe_keyword ('*', '/')
            если op равно None:
                перерыв
    
            термин = self.match ('коэффициент')
            если op == '*':
                rv * = срок
            еще:
                rv / = срок
    
        возврат RV 

    Далее мы реализуем «Фактор».Сначала мы попытаемся прочитать левую скобку, что означает, что есть выражение в скобках. Если мы действительно находим круглую скобку, мы читаем выражение и закрывающую круглую скобку и возвращаем значение выражения. В противном случае мы просто читаем и возвращаем число.

    Коэффициент определения
     (собственный):
        если self.maybe_keyword ('('):
            rv = self.match ('выражение')
            self.keyword (')')
    
            вернуть фургон
    
        вернуть self.match ('число')
     

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

    Распознавание чисел

    Чтобы распознать число, нам нужно посмотреть на отдельные символы в нашем тексте. Числа состоят из одной или нескольких цифр, за которыми может следовать десятичная часть. Десятичная часть имеет точку (.), За которой следует как минимум одна цифра. Кроме того, перед числами может стоять знак «+» или «-», например «-124,33».

    Мы реализуем метод number () следующим образом:

    Номер по умолчанию
     (сам):
        chars = []
    
        знак = себя.возможно_ключевое слово ('+', '-')
        если знак не Нет:
            chars.append (знак)
    
        chars.append (self.char ('0-9'))
    
        в то время как True:
            char = self.maybe_char ('0-9')
            если char равно None:
                перерыв
    
            chars.append (символ)
    
        если self.maybe_char ('.'):
            chars.append ('.')
            chars.append (self.char ('0-9'))
    
            в то время как True:
                char = self.maybe_char ('0-9')
                если char равно None:
                    перерыв
    
                chars.append (символ)
    
        rv = float (''. join (символы))
        вернуть фургон
     

    Хотя функция длинная, она довольно проста.У нас есть список символов и , в который мы помещаем символы числа. Сначала мы смотрим на любые символы «+» или «-», которые могут присутствовать перед числом. Если он присутствует, мы добавляем его в список. Затем мы ищем первую обязательную цифру числа и продолжаем поиск других цифр с помощью метода might_char () . Как только мы получим None might_char , мы знаем, что прошли набор цифр, и завершаем цикл.

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

    Интерфейс для нашего парсера

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

    , если __name__ == '__main__':
        parser = CalcParser ()
    
        в то время как True:
            пытаться:
                print (parser.parse (ввод ('>')))
            кроме KeyboardInterrupt:
                Распечатать()
            кроме (EOFError, SystemExit):
                Распечатать()
                перерыв
            кроме (ParseError, ZeroDivisionError) как e:
                print ('Ошибка:% s'% e)
     

    Если вы до сих пор следили за мной, поздравляем! Вы создали свой первый парсер рекурсивного спуска!

    Если вы используете локальную среду IDE, вы можете запустить свой код на этом этапе.В качестве альтернативы вы можете использовать игровую площадку ниже для запуска парсера. Вы можете вводить выражения через вкладку «Ввод» ниже:

    Вы также можете найти полный код здесь.

    Второй пример парсера: «расширенный» парсер JSON

    JSON - это формат обмена данными, который поддерживает основные структуры данных, такие как числа, строки и списки. Это очень широко используемый формат, хотя его простота означает отсутствие таких вещей, как комментарии, и строгость в отношении того, что он принимает. Например, у вас не может быть конечной запятой в списке или иметь явный знак «+» перед числом, чтобы указать его знак.

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

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

    Прежде чем мы реализуем нашу грамматику, давайте познакомимся с нашим расширенным форматом JSON:

     {
    # Комментарии начинаются с символа "#" и продолжаются до конца строки.# Вы можете пропускать кавычки в строках, если у них нет хешей,
    # скобки или запятые.
    Размер: 1,5x,
    
    # В списках и картах разрешены завершающие запятые.
    Что купить: {
    Яйца: 6,
    Хлеб: 4,
    Мясо: 2,
    },
    
    # И, конечно же, поддерживаются простые файлы JSON!
    «Имена»: [«Джон», «Мэри»],
    "Небо голубое?": Правда
    } 

    Производственные правила для грамматики JSON

    Наш расширенный JSON привязан к тем же типам данных, что и JSON. JSON имеет четыре примитивных типа данных - null, логические значения, числа и строки и два сложных типа данных - списки (или массивы) и карты (также называемые хешами или словарями).Списки и хэши выглядят так же, как в Python.

    Поскольку данные JSON будут состоять из одного из этих типов, вы можете написать производственные правила следующим образом:

    Затем вы можете разбить эти два типа на типы JSON:

    Эта грамматика хороша для синтаксического анализа обычного JSON, но для нашего случая требует небольшой настройки. Поскольку мы собираемся поддерживать строки без кавычек, это означает, что «1,5» (без кавычек) - это число, а «1.5x ”(опять же без кавычек) - это строка. Наша текущая грамматика будет читать «1,5» от «1,5x», а затем вызовет ошибку, потому что «x» не стоит после числа.

    Это означает, что сначала нам нужно получить полный набор символов без кавычек. Затем мы проанализируем символы и определим, является ли это числом или строкой. Итак, мы объединим числа и строки без кавычек в одну категорию «Без кавычек». Строки в кавычках подходят, поэтому мы разделим их на другую категорию - QuotedString.Наше измененное производственное правило для «PrimitiveType» теперь выглядит так:

    Кроме того, важен порядок следования правил. Поскольку у нас есть ключи без кавычек, мы должны сначала попробовать разобрать текст как нулевое или логическое. В противном случае мы можем признать «null» или «true» как строку без кавычек.

    Реализация синтаксического анализатора и обработка комментариев

    Чтобы реализовать синтаксический анализатор JSON, мы начнем с расширения базового класса синтаксического анализатора следующим образом:

     класс JSONParser (парсер):
    #...
     

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

     def eat_whitespace (сам):
        is_processing_comment = Ложь
    
        пока self.pos 
     

    Здесь нам нужно отслеживать, обрабатываем ли мы пробелы или комментарий. Мы перебираем текст посимвольно, проверяя, есть ли у нас пробелы или # . Когда есть # , мы обновляем is_processing_comment до True , и в следующих итерациях цикла while мы можем безопасно отбросить все символы, пока не дойдем до конца строки. Однако при обработке пробельных символов мы должны остановиться, как только появится непробельный символ.

    Далее мы реализуем производственные правила и метод start () . Во входном тексте будет содержаться тип JSON, поэтому мы просто вызовем any_type () в методе start () :

     def start (self):
        вернуть self.match ('any_type')
    
    def any_type (сам):
        return self.match ('сложный_тип', 'примитивный_тип')
    
    def примитивный_тип (сам):
        return self.match ('null', 'boolean', 'quoted_string', 'без кавычек')
    
    def комплексный_тип (сам):
        вернуть себя.match ('список', 'карта')
     

    Списки и карты синтаксического анализа

    Ранее мы видели, как анализировать списки, разделенные запятыми. Списки JSON представляют собой просто список, разделенный запятыми, с прикрепленными к ним квадратными скобками, и список может содержать ноль или более элементов. Давайте посмотрим, как вы реализуете синтаксический анализ списка:

     список def (сам):
        rv = []
    
        self.keyword ('[')
        в то время как True:
            item = self.maybe_match ('любой_тип')
            если элемент None:
                перерыв
    
            rv.append (элемент)
    
            если не сам.возможно_ключевое слово (','):
                перерыв
    
        self.keyword (']')
        возврат RV 

    Мы начинаем с считывания начальной квадратной скобки, за которой следует элемент из списка, используя self.maybe_match ('any_type') . Если нам не удалось получить элемент, это означает, что мы, вероятно, закончили просмотр всех элементов, поэтому мы выходим из цикла. В противном случае мы добавляем элемент в список. Затем мы пытаемся прочитать запятую из списка, и отсутствие запятой также означает, что мы закончили со списком.

    Точно так же карты - это просто разделенные запятыми списки «пар» с фигурными скобками, где пара - это строковый ключ, за которым следует двоеточие (:) и значение. В отличие от Python dict s, который может иметь любой «хешируемый» тип в качестве ключа (который включает int s и кортеж s), JSON поддерживает только строковые ключи.

    Вот как вы реализуете правила для карт и пар:

     def map (self):
        rv = {}
    
        self.keyword ('{')
        в то время как True:
            item = self.maybe_match ('пара')
            если элемент None:
                перерыв
    
            rv [элемент [0]] = элемент [1]
    
            если не сам.возможно_ключевое слово (','):
                перерыв
    
        self.keyword ('}')
        вернуть фургон
    
    def пара (self):
        ключ = self.match ('цитируемая_строка', 'без кавычек')
    
        если тип (ключ) не str:
            поднять ParseError (
                self.pos + 1,
                'Ожидаемая строка, но есть номер',
                self.text [self.pos + 1]
            )
    
        self.keyword (':')
        значение = self.match ('любой_тип')
    
        ключ возврата, значение 

    В pair () мы пытаемся прочитать «QuotedString» или «Unquoted» для ключа. Как мы упоминали ранее, «Без кавычек» может возвращать либо число, либо строку, поэтому мы явно проверяем, является ли прочитанный нами ключ строкой.Затем pair () возвращает кортеж с ключом и значением, а map () вызывает pair () и добавляет их в Python dict.

    Распознавание нулевых и логических значений

    Теперь, когда основные части нашего синтаксического анализатора реализованы, давайте начнем с распознавания простых токенов, таких как null и логические значения:

     def null (сам):
        self.keyword ('нуль')
        return None
    
    def boolean (сам):
        логическое = self.keyword ('истина', 'ложь')
        return boolean [0] == 't'
     

    Python None является ближайшим аналогом JSON null .В случае логических значений первых символов достаточно, чтобы определить, является ли это истинным или ложным , и мы возвращаем результат сравнения.

    Распознавание строк и чисел без кавычек

    Прежде чем перейти к распознаванию строк без кавычек, давайте сначала определим набор допустимых символов. Мы опустим все, что считается особенным, например фигурные скобки, кавычки, двоеточия, хеши (поскольку они используются в комментариях) и обратную косую черту (поскольку они используются для escape-последовательностей)._`abcdefghijklmnopqrstuvwxyz | ~ '

    Следующий вопрос: как определить, является ли текст, который вы читаете, числом или строкой? Ответ - мы обманываем! Поскольку функции Python int () и float () могут принимать строку и возвращать число, мы будем использовать их, и если это приведет к ошибке ValueError , мы вернем строку. Что касается того, когда использовать int () или float () , мы воспользуемся простым правилом. Если текст содержит «E», «e» или «.» (Например, «12.3» или «2e10»), мы вызовем float () ; в противном случае мы будем использовать int () ._` | ~ - ' number_type = int chars = [self.char (accept_chars)] в то время как True: char = self.maybe_char (accept_chars) если char равно None: перерыв если символ в 'Ee.': number_type = float chars.append (символ) rv = '' .join (символы) .rstrip ('\ t') пытаться: вернуть number_type (rv) кроме ValueError: возврат RV

    Поскольку правила сопоставления обрабатываются функцией match () , она позаботится об удалении всех начальных пробелов перед запуском без кавычек () .Таким образом, мы можем быть уверены, что unquoted () не вернет строку, состоящую только из пробелов. Любые пробелы в конце удаляются, прежде чем мы проанализируем его как число или вернем саму строку.

    Распознавание строк в кавычках

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

    Мы будем поддерживать следующие escape-последовательности - \ b (backspace), \ f (перевод строки), \ n (newline), \ r (возврат каретки), \ t (tab ) и \ u (четыре шестнадцатеричных цифры) , где эти цифры используются для представления «кодовой точки» Unicode.Для всего остального, за которым следует символ обратной косой черты, мы игнорируем обратную косую черту. Это обрабатывает такие случаи, как использование обратной косой черты для экранирования самого себя ( \\ ) или экранирование кавычек ( \ ").

    Вот его код:

     def quoted_string (self):
        quote = self.char ('"\' ')
        chars = []
    
        escape_sequences = {
            'b': '\ b',
            'f': '\ f',
            'n': '\ n',
            'г': '\ г',
            'т': '\ т'
        }
    
        в то время как True:
            char = self.char ()
            если char == цитата:
                перерыв
            elif char == '\\':
                escape = self.char ()
                если escape == 'u':
                    code_point = []
                    для i в диапазоне (4):
                        code_point.append (self.char ('0-9a-fA-F'))
    
                    chars.append (chr (int (''. join (code_point), 16)))
                еще:
                    chars.append (escape_sequences.get (символ, символ))
            еще:
                chars.append (символ)
    
        return '' .join (символы)
     

    Сначала мы читаем цитату, а затем читаем дополнительные символы в цикле. Если этот символ представляет собой тот же тип кавычек, что и первый прочитанный символ, мы можем прекратить дальнейшее чтение и вернуть строку, соединив элементы символов .

    В противном случае мы проверяем, является ли это escape-последовательностью, обрабатываем ее вышеупомянутым способом и добавляем ее к chars . Наконец, если он не соответствует ни одному из них, мы рассматриваем его как обычный символ и добавляем его в наш список символов.

    Интерфейс для нашего парсера JSON

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

    , если __name__ == '__main__':
        import sys
        из pprint импорт pprint
    
        parser = JSONParser ()
    
        пытаться:
            pprint (parser.анализ (sys.stdin.read ()))
        кроме ParseError как e:
            print ('Ошибка:' + str (e))
     

    Чтобы мы могли читать несколько строк, мы использовали sys.stdin.read () . Если вы собираетесь запускать это локально, вы можете ввести текст и использовать Ctrl + D, чтобы программа не запрашивала дополнительные данные. В противном случае вы можете использовать этот исполняемый фрагмент:

    Вы можете найти полный код здесь.

    Построение других видов синтаксических анализаторов

    Теперь, когда мы построили два синтаксических анализатора, у вас, возможно, есть неплохое представление о том, как использовать собственный для решения поставленной задачи.Хотя каждый язык уникален (и, следовательно, их синтаксические анализаторы), есть некоторые распространенные ситуации, которые мы не рассмотрели. Для краткости мы не будем рассматривать какой-либо код в этих разделах.

    Языки, чувствительные к пробелам

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

    Примером такого языка может быть то, что «a = 10» отличается от «a = 10». В bash и некоторых других оболочках «a = 10» устанавливает переменную среды, тогда как «a = 10» запускает программу «a» с «=» и «10» в качестве аргументов командной строки. Вы даже можете комбинировать их! Рассмотрим это:

     а = 10 б = 20 с = 30 

    Устанавливает переменные среды «a» и «b» только для программы «c». Единственный способ разобрать такой язык - обработать пробелы вручную, и вам придется удалить все вызовы eat_whitespace () в keyword () и match () .Вот как можно написать производственные правила:

    Отступ на основе пробелов

    Такие языки, как C и Javascript, используют фигурные скобки для обозначения тела циклов и операторов if. Однако Python использует отступы для той же цели, что усложняет синтаксический анализ.

    Один из способов справиться с этим - ввести такой термин, как «INDENT», чтобы отслеживать операторы с отступом. Чтобы увидеть, как это будет работать, рассмотрим следующее производственное правило:

    После соответствия условию наш синтаксический анализатор будет искать INDENT.Поскольку это первая попытка сопоставления INDENT, она берет любые пробелы (кроме символов новой строки), которые появляются вместе с непробельным символом (поскольку это будет частью оператора).

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

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

    пожаловаться на это объявление

    Если вам понравился этот пост, поделитесь им 🙂

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

    Диссертация

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

    Автор
    Хильке Рекман
    Дата
    18 марта 2009
    Ссылки
    Полный текст в репозитории Лейденского университета

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

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

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

    .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *