stringtranslate.com

Анализатор приоритета операторов

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

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

Связь с другими парсерами

Парсер приоритета операторов — это простой парсер сдвига-свертки , который способен анализировать подмножество грамматик LR(1) . Точнее, парсер приоритета операторов может анализировать все грамматики LR(1), где два последовательных нетерминала и эпсилон никогда не появляются в правой части любого правила.

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

Raku помещает парсер приоритета операторов между двумя парсерами рекурсивного спуска , чтобы достичь баланса скорости и динамичности. Парсеры C и C++ GCC , которые являются вручную закодированными парсерами рекурсивного спуска, оба ускоряются парсером приоритета операторов, который может быстро проверять арифметические выражения. Парсеры приоритета операторов также встроены в парсеры, сгенерированные компилятором-компилятором , чтобы заметно ускорить подход рекурсивного спуска к разбору выражений. [1]

Метод приоритетного восхождения

Метод повышения приоритета — это компактный, эффективный и гибкий алгоритм для анализа выражений, впервые описанный Мартином Ричардсом и Колином Уитби-Стревенсом. [2]

Грамматика выражений с инфиксной нотацией в формате EBNF обычно выглядит следующим образом:

выражение  :: = выражение-равенства выражение-равенства :: = аддитивное-выражение ( ( ' == ' | '! = ' ) аддитивное-выражение ) * аддитивное-выражение :: = мультипликативное-выражение ( ( '+' | '-' ) мультипликативное-выражение ) * мультипликативное-выражение :: = первичный ( ( ' * ' | ' / ' ) первичный ) * первичный :: = ' ( ' выражение ' ) ' | ЧИСЛО | ПЕРЕМЕННАЯ | '-' первичный                                             

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

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

Алгоритм не является чистым парсером приоритета операторов, как алгоритм сортировочной станции Дейкстры. Он предполагает, что первичный нетерминал анализируется в отдельной подпрограмме, как в парсере рекурсивного спуска.

Псевдокод

Псевдокод алгоритма выглядит следующим образом. Анализатор начинается с функции parse_expression . Уровни приоритета больше или равны 0.

parse_expression()  возвращает parse_expression_1(parse_primary(), 0)
parse_expression_1(lhs, min_precedence)  lookahead  := peek next token , в то время как  lookahead — это бинарный оператор, приоритет которого >= min_precedence  op  := lookahead перейти к следующему токену rhs  := parse_primary () lookahead  := peek next token , в то время как  lookahead — это бинарный оператор, приоритет которого выше чем op 's, или правоассоциативный оператор приоритет которого равен приоритету op' s rhs  := parse_expression_1 ( rhs , приоритет op + (1, если приоритет опережающего просмотра больше, иначе 0)) lookahead  := peek next token lhs  := результат применения op с операндами lhs и rhs  return  lhs

Обратите внимание, что в случае такого правила производства (где оператор может появляться только один раз):

выражение-равенства  :: = аддитивное-выражение ( ' == ' | '! = ' ) аддитивное-выражение       

алгоритм необходимо модифицировать так, чтобы он принимал только бинарные операторы, приоритет которых > min_precedence .

Пример выполнения алгоритма

Ниже приведен пример выполнения выражения 2 + 3 * 4 + 5 == 19. Мы даем приоритет 0 выражениям равенства, 1 — аддитивным выражениям, 2 — мультипликативным выражениям.

parse_expression_1 ( lhs = 2, min_precedence = 0)

  • токен опережающего просмотра — * с приоритетом 2. выполняется вход во внешний цикл while.
  • op - * (приоритет 2), а входные данные расширены
  • правая часть равна 4
  • Следующий токен — + с приоритетом 1. Внутренний цикл while не вводится.
  • левой части присвоено 3*4 = 12
  • следующий токен — + с приоритетом 1. внешний цикл while остается.
  • 12 возвращается.

Возвращается 1.

анализ Пратта

Другой синтаксический анализатор приоритетов, известный как синтаксический анализ Пратта, был впервые описан Воганом Праттом в статье 1973 года "Top Down Operator Precedence", [3] на основе рекурсивного спуска . Хотя он предшествовал восхождению приоритетов, его можно рассматривать как обобщение восхождения приоритетов. [4]

Первоначально Пратт разработал синтаксический анализатор для реализации языка программирования CGOL , и он был гораздо более подробно рассмотрен в магистерской диссертации под его руководством. [5]

Учебники и реализации:

Альтернативные методы

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

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

Полные скобки

Другой подход заключается в том, чтобы сначала полностью заключить выражение в скобки, вставив несколько скобок вокруг каждого оператора, так что они приведут к правильному приоритету даже при разборе линейным анализатором слева направо. Этот алгоритм использовался в раннем компиляторе FORTRAN I : [7]

Компилятор Fortran I расширил бы каждый оператор последовательностью скобок. В упрощенной форме алгоритма это было бы

Хотя алгоритм не был очевиден, он был верным, и, по словам Кнута , «полученная формула правильно заключена в скобки, хотите верьте, хотите нет». [8]

Пример кода простого приложения на языке C, которое обрабатывает скобки основных математических операторов ( +, -, *, /, ^, (и )):

#include <stdio.h> #include <string.h>  // Граница аргумента командной строки — это наш лексер. int main ( int argc , char * argv []) { int i ; printf ( "((((" ); for ( i = 1 ; i != argc ; i ++ ) { // strlen(argv[i]) == 2 if ( argv [ i ] && ! argv [ i ][ 1 ]) { switch ( * argv [ i ]) { case '(' : printf ( "((((" ); continue ; case ')' : printf ( "))))" ); continue ; case '^' : printf ( ")^(" ); continue ; case '*' : printf ( "))*((" ); continue ; case '/' : printf ( "))/((" ); continue ; case '+' : // унарная проверка: либо первая, либо имелась операция, ожидающая вторичный аргумент if ( i == 1 || strchr ( "(^*/+-" , * argv [ i -1 ])) printf ( "+" ); else printf ( ")))+(((" ); continue ; case '-' : if ( i == 1 || strchr ( "(^*/+-" , * argv [ i -1 ])) printf ( "-" ); else printf ( ")))-(((" ); continue ; } } printf ( "%s" ,argv [ i ]); } printf ( ")))) \n " ); возврат                                                                             0 ; }

Сначала вам нужно скомпилировать вашу программу. Предполагая, что ваша программа написана на языке C, а исходный код находится в файле с именем program.c, вы можете использовать следующую команду:

программа gcc.c -o программа

Приведенная выше команда указывает gcc скомпилировать program.c и создать исполняемый файл с именем program.

Команда для запуска программы с параметрами, например; a * b + c ^ d / e

./программа а '*' б + в '^' г / е

он производит

((((а))*((б)))+(((с)^(г))/((д))))

как вывод на консоль.

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

- а ^ 2

производит этот вывод

((((-а)^(2))))

что, вероятно, не совсем то, что имелось в виду.

Ссылки

  1. ^ ab Harwell, Sam (29.08.2008). "Парсер приоритета операторов". ANTLR3 Wiki . Получено 25.10.2017 .
  2. ^ Ричардс, Мартин; Уитби-Стревенс, Колин (1979). BCPL — язык и его компилятор . Cambridge University Press. ISBN 9780521219655.
  3. ^ Пратт, Воган. «Приоритет операторов сверху вниз». Труды 1-го ежегодного симпозиума ACM SIGACT-SIGPLAN по принципам языков программирования (1973).
  4. ^ Норвелл, Теодор. "Анализ выражений методом рекурсивного спуска". www.engr.mun.ca . Цель этой статьи — [... начать] с восхождения по приоритету и переработать его для использования шаблона команды, пока мы не придем к синтаксическому анализатору Пратта. [Это автор, который придумал термин "восхождение по приоритету".]
  5. ^ Ван Де Вантер, Майкл Л. «Формализация и доказательство корректности языковой системы CGOL». (Магистерская диссертация). Технический отчет лаборатории компьютерных наук Массачусетского технологического института MIT-LCS-TR-147 (Кембридж, Массачусетс). 1975.
  6. ^ Крокфорд, Д. (21.02.2007). «Приоритет операторов сверху вниз».
  7. ^ Падуа, Дэвид (2000). "Компилятор Fortran I" (PDF) . Вычислительная техника в науке и технике . 2 (1): 70–75. Bibcode : 2000CSE.....2a..70P. doi : 10.1109/5992.814661.
  8. ^ Кнут, Дональд Э. (1962). «ИСТОРИЯ НАПИСАНИЯ КОМПИЛЯТОРОВ». Компьютеры и автоматизация . 11 (12). Эдмунд К. Беркли: 8–14.

Внешние ссылки