Хобрук: Ваш путь к мастерству в программировании

Разбор, какой метод выбрать?

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

Вот моя текущая грамматика: http://img11.hostingpics.net/pics/273965Capturedcran20130417192526.png

Я думал о создании нисходящего парсера LL(1), как описано здесь: http://dragonbook.stanford.edu/lecture-notes/Stanford-CS143/07-Top-Down-Parsing.pdf

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

Спасибо, Ментинет.


  • Вам нужно быть эффективным с точки зрения кодирования вашего синтаксического анализатора, или вы хотите, чтобы код был эффективным с точки зрения синтаксического анализа? В моем почти двухдесятилетнем опыте написания вещей, требующих синтаксических анализаторов, никогда не было случая, чтобы скорость синтаксического анализа была важна в измеримой степени. То, что я делал с проанализированным AST, всегда было ключевым фактором в определении производительности. 17.04.2013
  • Самое важное для меня — быть эффективным с точки зрения кодирования, потому что мы (я работаю в паре) уже сделали много работы в Clean, но у нас нет опыта работы с этим языком, и сейчас действительно сложно идти дальше. Так что мы немного опоздали, и чем быстрее у нас будет что-то работающее, тем лучше. Затем нам также придется построить AST. 17.04.2013
  • Тогда я бы использовал ANTLR: он создает очень хорошие парсеры рекурсивного спуска, имеет цель C и очень прост в освоении. 17.04.2013
  • Дело в том, что мы можем использовать библиотеки, но они не обязаны выполнять всю работу. Итак, я думаю, что попытаюсь реализовать парсер с рекурсивным спуском. 18.04.2013
  • Реализуйте свой собственный синтаксический анализ Packrat: он довольно тривиален и очень прост в использовании - PEG почти интуитивно понятен. 18.04.2013
  • Я второй использую PEG/Packrat. Это просто делает все намного проще, поэтому большую часть вашего времени можно потратить на интерпретацию/генерацию кода вместо борьбы с грамматикой и генератором синтаксического анализатора. Я бы понял, что вам нужно, чтобы сгенерированный парсер был на C, но не генератор парсера. Если это так, вы можете взглянуть на PegJS, PyParsing или Grako. Если вы должны использовать C, тогда есть Rats. 18.04.2013

Ответы:


1

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

17.04.2013
  • Я подвергаю сомнению все эти утверждения. Я не знаю, что синтаксический анализ LR значительно эффективнее, чем LL. Однако он более мощный. Парсеры LR создаются с помощью генераторов парсеров, что легко, в то время как некоторые парсеры LL, такие как рекурсивный спуск, должны создаваться вручную, что значительно больше сложно, а также гораздо более подвержены ошибкам. 18.04.2013
  • Более того, мы должны сами писать код, чтобы использовать библиотеки, а не программу, которая будет генерировать код за нас. Тогда, учитывая грамматику, какие вещи нужно изменить, помимо левой рекурсии. Несколько советов по этому поводу? Спасибо большое. 18.04.2013
  • @EJP, синтаксические анализаторы LR не являются более мощными, потому что с ними действительно сложно получить бесконечный просмотр вперед (и тривиально с LL). 18.04.2013
  • Синтаксические анализаторы LR более эффективны с точки зрения использования памяти, но только при наличии специального управления таблицами синтаксического анализа. Парсеры LR наименее эффективны с точки зрения усилий программиста. 18.04.2013
  • Парсеры @SK-logic LR более мощные, потому что они анализируют правый контекст, а не левый контекст. ЛР(1) › НЛ(1). Вам не нужен бесконечный взгляд вперед. 22.04.2013
  • @Apalala Я уже оспаривал это утверждение. Вот для чего нужны генераторы парсеров. 22.04.2013
  • @EJP, не мог бы ты объяснить? Существуют грамматики, которым нужно требуется бесконечный просмотр вперед, и они не могут быть представлены в LR(n). 22.04.2013
  • Генераторы @EJP Parsers не знают, как отлаживать грамматики, ни сами. Конфликт сдвиг-свертка — это то, что генератор синтаксического анализатора понимает очень хорошо, а реализатор — нет. И это все, что я могу сказать об этом! 22.04.2013
  • @SK-logic Поскольку LR(k) является надмножеством LL(k), эти грамматики также не могут быть представлены в LL(k). 24.04.2013
  • @Apalala Актуальность вашего комментария ускользает от меня. 24.04.2013
  • @EJP, я говорю о LL (бесконечность) и его расширенном наборе, PEG. 24.04.2013

  • 2

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

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

    Если вы действительно так склонны или если вы думаете о написании парсера самостоятельно просто для развлечения и опыта, лучшими современными алгоритмами являются SGLR, GLL и Packrat. Каждое из них представляет собой квинтэссенцию алгоритмических исследований, длившихся полвека, так что не ждите, что разберетесь с ними полностью в мгновение ока, и не ждите ничего хорошего от первых двух «исправлений», которые вы придумаете. с участием. Однако, если вы найдете хорошее улучшение, не стесняйтесь поделиться своими выводами с авторами или опубликовать их иным образом!

    19.04.2013

    3

    Здесь много ответов, но они все путают. Да, есть парсеры LL и LR, но это не ваш выбор.

    У вас есть грамматика. Существуют инструменты, которые автоматически создают парсер для вас по заданной грамматике. Почтенные Yacc и Бизон сделайте это. Они создают парсер LR (на самом деле LALR). Существуют также инструменты, которые создают для вас парсер LL, например ANTLR. Недостатком подобных инструментов является их негибкость. Их автоматически генерируемые сообщения об ошибках синтаксиса отстой, восстановление ошибок затруднено, а более старые побуждают вас учитывать ваш код одним способом, который оказывается неправильным. Правильный способ - заставить ваш синтаксический анализатор выдать абстрактное синтаксическое дерево, а затем заставить компилятор сгенерировать код из него. Инструменты хотят, чтобы вы смешивали код парсера и компилятора.

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

    Причина существования синтаксических анализаторов LL, LALR и LR заключается в том, что работа по созданию синтаксического анализатора LR требовала от современного компьютера больших затрат времени и памяти. (Обратите внимание, что для этого требуется сгенерировать парсер, что происходит только тогда, когда вы его пишете. Сгенерированный парсер работает очень быстро.) Но это было очень давно. Генерация синтаксического анализатора LR(1) занимает гораздо меньше 1 ГБ ОЗУ для умеренно сложного языка, а на современном компьютере — меньше секунды. По этой причине лучше использовать автоматический генератор синтаксических анализаторов LR, например Hyacc.

    Другой вариант - вы пишете свой собственный парсер. В этом случае есть только один выбор: синтаксический анализатор LL. Когда люди здесь говорят, что писать LR сложно, они преуменьшают значение. Человеку практически невозможно вручную создать синтаксический анализатор LR. Вы можете подумать, что это означает, что если вы пишете свой собственный синтаксический анализатор, вы вынуждены использовать грамматики LL(1). Но это не совсем так. Поскольку вы пишете код, вы можете обманывать. Вы можете смотреть вперед произвольное количество символов, и поскольку вам не нужно ничего выводить, пока вы не будете готовы, абстрактное синтаксическое дерево не должно соответствовать используемой вами грамматике. Эта способность обманывать компенсирует всю утраченную силу между LL и LR(1), а часто и часть.

    У рукописных парсеров, конечно, есть свои недостатки. Нет никакой гарантии, что ваш синтаксический анализатор действительно соответствует вашей грамматике, или, если уж на то пошло, нет проверки правильности вашей грамматики (т.е. распознает язык, который, по вашему мнению, он делает). Они длиннее и еще хуже побуждают вас смешивать код разбора с кодом компиляции. Кроме того, очевидно, что они реализованы только на одном языке, в то время как генератор синтаксических анализаторов часто выдает результаты на нескольких разных языках. Даже если это не так, таблица синтаксического анализа LR может быть представлена ​​в виде структуры данных, содержащей только константы (скажем, в формате JSON), а фактический синтаксический анализатор состоит всего из 100 строк кода или около того. Но есть и положительные стороны написанного от руки парсера. Поскольку вы написали код, вы знаете, что происходит, поэтому легче выполнять восстановление после ошибок и генерировать разумные сообщения об ошибках.

    В конце концов, компромисс часто работает следующим образом:

    • Для разовых заданий лучше использовать генератор синтаксических анализаторов LR(1). Генератор проверит вашу грамматику, избавит вас от работы, а современные генераторы напрямую разбивают абстрактное синтаксическое дерево, а это именно то, что вам нужно.
    • Для хорошо отполированных инструментов, таких как mcc или gcc, используйте написанный от руки анализатор LL. Вы все равно будете писать множество модульных тестов, чтобы защитить свою спину, восстановление после ошибок и сообщения об ошибках намного проще исправить, и они могут распознавать более широкий класс языков.

    Единственный другой вопрос, который у меня есть: почему C? Компиляторы обычно не являются критичным ко времени кодом. Существуют очень хорошие пакеты синтаксического анализа, которые позволят вам выполнить работу за половину кода, если вы хотите, чтобы ваш компилятор работал немного медленнее — мой собственный Lrparsing, например. Имейте в виду, что «немного медленнее» здесь означает «едва заметное для человека». Я предполагаю, что ответ таков: «задание, над которым я работаю, указывает C». Чтобы дать вам представление, вот насколько простым становится переход от вашей грамматики к дереву синтаксического анализа, когда вы ослабляете требование. Эта программа:

    #!/usr/bin/python
    
    from lrparsing import *
    
    class G(Grammar):
      Exp = Ref("Exp")
      int = Token(re='[0-9]+')
      id = Token(re='[a-zA-Z][a-zA-Z0-9_]*')
      ActArgs = List(Exp, ',', 1)
      FunCall = id + '(' + Opt(ActArgs) + ')'
      Exp = Prio(
          id | int | Tokens("[]", "False True") | Token('(') + List(THIS, ',', 1, 2) + ')' |
          Token("! -") + THIS,
          THIS << Tokens("* / %") << THIS,
          THIS << Tokens("+ -") << THIS,
          THIS << Tokens("== < > <= >= !=") << THIS,
          THIS << Tokens("&&") << THIS,
          THIS << Tokens("||") << THIS,
          THIS << Tokens(":") << THIS)
      Type = (
          Tokens("", "Int Bool") |
          Token('(') + THIS + ',' + THIS + ')' |
          Token('[') + THIS + ']')
      Stmt = (
          Token('{') + THIS * Many + '}' |
          Keyword("if") + '(' + Exp + ')' << THIS + Opt(Keyword('else') + THIS) |
          Keyword("while") + '(' + Exp + ')' + THIS |
          id + '=' + Exp + ';' |
          FunCall + ';' |
          Keyword('return') + Opt(Exp) + ';')
      FArgs = List(Type + id, ',', 1)
      RetType = Type | Keyword('void')
      VarDecl = Type + id + '=' + Exp + ';'
      FunDecl = (
          RetType + id + '(' + Opt(FArgs) + ')' +
          '{' + VarDecl * Many + Stmt * Some + '}')
      Decl = VarDecl | FunDecl
      Prog = Decl * Some
      COMMENTS = Token(re="/[*](?:[^*]|[*][^/])*[*]/") | Token(re="//[^\n\r]*")
      START = Prog
    
    EXAMPLE = """\
    Int factorial(Int n) {
      Int result = 1;
      while (n > 1) {
        result = result * n;
        n = n - 1;
      }
      return result;
    }
    """
    
    parse_tree = G.parse(EXAMPLE)
    print G.repr_parse_tree(parse_tree)
    

    Производит этот вывод:

    (START (Prog (Decl (FunDecl
      (RetType (Type 'Int'))
      (id 'factorial') '('
      (FArgs
        (Type 'Int')
        (id 'n')) ')' '{'
      (VarDecl
        (Type 'Int')
        (id 'result') '='
        (Exp (int '1')) ';')
      (Stmt 'while' '('
        (Exp
          (Exp (id 'n')) '>'
          (Exp (int '1'))) ')'
        (Stmt '{'
          (Stmt
            (id 'result') '='
            (Exp
              (Exp (id 'result')) '*'
              (Exp (id 'n'))) ';')
          (Stmt
            (id 'n') '='
            (Exp
              (Exp (id 'n')) '-'
              (Exp (int '1'))) ';') '}'))
      (Stmt 'return'
        (Exp (id 'result')) ';') '}'))))
    
    04.05.2013

    4

    Спасибо за все эти советы, но мы, наконец, решили создать собственный анализатор рекурсивного спуска, используя точно такой же метод, как здесь: http://www.cs.binghamton.edu/~zdu/parsdemo/recintro..html

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

    Теперь у нас есть сборка AST внутри этих рекурсивных функций. У вас есть предложения? Советы? Спасибо большое.

    25.04.2013
  • Вы не должны вставлять в свой ответ еще один вопрос, так как он, скорее всего, будет удален как не ответ. Задайте другой отдельный вопрос и отредактируйте этот, чтобы он был просто ответом, который вы можете принять. 13.06.2015
  • Новые материалы

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

    Генерация ваших собственных удивительных QR-кодов с использованием Python
    QR-код (код быстрого ответа) — это разновидность матричных штрих-кодов (или двумерных штрих-кодов), изобретенных в 1994 году японской автомобильной компанией Denso Wave . Штрих-код —..

    Прогресс в технологии Трансформеров часть 3
    Многомасштабный управляющий сигнальный преобразователь для бесфазного синтеза движения (arXiv) Автор: Линтао Ван , Кун Ху , Лей Бай , Юй Дин , Ваньли Оуян , Чжиюн Ван . Аннотация:..

    Представляем поддержку компонентов Vue.js. Мгновенный HMR и многое другое.
    Хотя у FuseBox уже был плагин Vue, он был базовым и не имел многих функций, которые делали работу с Vue.js такой приятной. Однако с этим выпуском мы рады сообщить, что в FuseBox..

    Приключения в Javascript, часть 1
    Я продолжаю думать о том, чтобы писать больше, но чем больше я думаю об этом, тем меньше я это делаю. Итак, сегодня я перестал думать и начал писать. Отсюда можно только спускаться… В..

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

    Учебные заметки JavaScript Object Oriented Labs
    Вот моя седьмая неделя обучения программированию. После ruby ​​и его фреймворка rails я начал изучать самый популярный язык интерфейса — javascript. В отличие от ruby, javascript — это более..