Приоритет функции в алгоритме Шунтирования

10

Я работаю с помощью алгоритма Shunting-yard , как описано в Википедии.

Описание алгоритма при работе с операторами выглядит следующим образом:

Если токен является оператором o1, то:

в то время как есть токен оператора, o2, на вершине стека операторов, и либо

o1 is left-associative and its precedence is less than or equal to
that of o2, or

o1 is right associative, and has precedence less than that of o2,

затем вытолкните o2 из стека операторов в очередь вывода;

нажмите o1 на стек оператора.

Однако они приводят следующий пример:

Входные данные: sin max 2 3 / 3 * 3.1415

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

Token |        Action       |   Output (in RPN) |   Operator Stack
...
/     | Pop token to output | 2 3 max           | / sin 
...

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

Нет никакого объяснения относительно того, действительно ли это так. Итак, для Shunting-yardалгоритма, каков приоритет функции? Являются ли функции правыми или левыми ассоциативными? Или Википедия просто неполная / неточная?

MirroredFate
источник

Ответы:

5

Я считаю, что прямой ответ заключается в том, что функции не являются операторами. Со страницы, на которую вы ссылаетесь:

Если токен является токеном функции, поместите его в стек.

Это все, что нужно сказать, поскольку регистр функций (префикс к постфиксу) намного проще, чем регистр оператора (инфикс к постфиксу).

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

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

Ixrec
источник
Их алгоритм правильный, тогда; это их пример, который неверен. Инфиксная запись должна включать скобки, заключающие в себе функции:sin( max( 2 3) / 3 * 3.1415)
MirroredFate
Я не уверен, что назвал бы это неправильно, но это сильный аргумент в пользу языков, которые требуют круглых скобок и запятых во всех вызовах функций.
Ixrec
Я думаю, что это неправильно, так как невозможно проанализировать инфикс, используя алгоритм, как они его описывают.
MirroredFate
@Ixrec Я не вижу строки «Если токен является токеном функции, поместите его в стек». на странице Википедии. Может быть, его уже отредактировано. Но вы имеете в виду, что я могу трактовать функцию как число в алгоритме?
Абхинав
Полагаю, в описании алгоритма в статье в Википедии есть ошибка (до сегодняшнего дня). После фразы if there is a left parenthesis at the top of the operator stack, then: pop the operator from the operator stack and discard itследует добавить еще одну строку: если сейчас есть имя функции в верхней части стека, извлеките его из стека и вставьте в вывод . Другими словами, левый '(' всегда должен появляться вместе с именем функции, если они размещены вместе, как предполагает синтаксис функции: " name(".
Стэн
3

Есть два разных случая для рассмотрения, в зависимости от синтаксиса вашего языка. Если ваш язык использует скобки для обозначения приложения функции (например f(2+1)), то приоритет не имеет значения. Функция должна быть помещена в стек и выдана после (для примера выше, результат равен 2 1 + f). В качестве альтернативы вы можете рассматривать функцию как значение и выводить ее немедленно и выводить операцию вызова функции после закрывающей круглой скобки (которая в противном случае должна обрабатываться так же, как любая другая скобка), например f 2 1 + $, где $находится операция вызова функции.

Если ваш язык, однако, не использует круглые скобки для обозначения вызова функции, а вместо этого помещает аргумент непосредственно после функции без какой-либо специальной пунктуации (например f 2 + 1), как это, очевидно, имеет место в примере из Википедии, тогда все немного сложнее. Обратите внимание, что выражение, которое я только что привел в качестве примера, неоднозначно: применяется ли f к 2 и 1, добавляется к результату, или мы добавляем 2 и 1 вместе, а затем вызываем f с результатом?

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

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

Тогда единственное, что вам нужно решить - это приоритет применения функций. Выбор за вами, но на каждом используемом мной языке, который работает подобным образом, он был наиболее сильным обязательным оператором в языке и был правильно ассоциативным. (Единственный интересный вариант - это Haskell, который, помимо описанной версии с сильной привязкой, также имеет синоним для него с символом, $который является наиболее слабой связывающей операцией в языке, позволяя выражениям типа f 2 + 1применять f к 2 и f $ 2 + 1применять это ко всему остальному выражению)

Жюль
источник
3

Я реализовал запрашиваемые «функции в маневровом дворе» после прочтения оригинального мышления Дейкстры (стр. 7-11 в статье компилятора Algol 60, https://ir.cwi.nl/pub/9251 ), и мне понадобилось надежное решение. сделал следующее:

синтаксический анализ:

  • Нажмите дескриптор функции
  • Вставьте в начале скобки левую скобку «[» так же, как в начале скобки подвыражения.
  • Прочитать последовательность сбалансированных списков аргументов "(" to ")" из ввода
  • Нажмите это к выходному токену
  • Вставьте правую скобку конца арки "]" так же, как его "компенсационная закрывающая скобка"

Инфикс-постфикс (маневровый двор):

  • Добавьте еще один стек, стек функций, как стек операторов
  • При сканировании имени функции поместите информацию о функции в стек функций
  • Когда видна правая скобка конца аргументов, выведите стек функций для вывода

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

Примеры выглядят как «sqrt (3 ^ 2 + 4 ^ 2)», который становится «3 2 ^ 4 2 ^ + sqrt» и, в конечном счете, «5», что является аргументом программы. Это bignum, поэтому "" binomial (64, 32) / gcd (binomial (64, 32), binomial (63, 31)) "==> большие вещи ==>" 2 "полезно." 123456 ^ 789 " на моем MacBookPro - 40 173 цифры, и время показывает «оценка = 0,000390 сек», так быстро.

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

Майкл
источник