Однозначная грамматика для функций высшего порядка - PullRequest
0 голосов
/ 23 апреля 2020

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

<type> ::= <base_type> <optional_array_size>
<optional_array_size> ::= "[" <INTEGER_LITERAL> "]" | ""

<base_type> ::= <integer_type> | <real_type> | <function_type>

<function_type> ::= "(" <params> ")" "->" <type>

<params> ::= <type> <params_tail> | ""
<params_tail> ::= "," <type> <params_tail> | ""

, так что я могу определять типы, такие как Integer[42], Real или (Integer, Real) -> Integer. Это все хорошо, но я бы хотел, чтобы мои функции были гражданами первого сорта. Учитывая приведенную выше грамматику, я не могу иметь массивы функций, так как это только превратит возвращаемый тип в массив. (Integer, Real) -> Integer [42] будет не массивом из 42 функций, а одной функцией, которая возвращает массив из 42 целых чисел.

Я подумывал добавить необязательные скобки вокруг типов функций ((Integer, Real) -> Integer)[42], но это создает другую проблему (примечание: я использую синтаксический анализатор рекурсивного спуска сверху вниз, поэтому моя грамматика должна быть LL (1)). :

<function_type> ::= "(" <function_type_tail>

<function_type_tail> ::= <params> ")" "->" <type>
                       | "(" <params> ")" "->" <type> ")"

Проблема в том, что first(params) содержит "(", поскольку типы функций могут передаваться в качестве параметров функции: ((Integer) -> Real, Real) -> Integer. Этот синтаксис был действителен до того, как я изменил грамматику, но теперь он больше не работает. Как я могу изменить мою грамматику, чтобы получить то, что я хочу?

1 Ответ

2 голосов
/ 24 апреля 2020

Это определенно сложная задача.

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

<type> ::= <base_type> <optional_array_size>
<base_type> ::= <function_type>
<function_type> ::= "(" <params> ")" "->" <type>

Неоднозначность, как я уверен, вы знаете, возникает из-за незнания, является ли [42] в ()->Integer[42] частью вершины -уровень <type> или вложенный <function_type>. Чтобы устранить неоднозначность, нам нужно четко указать, какая конструкция может принимать размер массива. (Здесь я добавил желаемое произведение, которое позволяет заключить в скобки <type>):

<type>        ::= <atomic_type> <optional_array_size>
              |   <function_type>
<opt_array_size>  ::= ""
              |   <array_size>
<atomic_type> ::= <integer_type>
              |   <real_type>
              |   "(" <type> ")"
<function_type> ::= "(" <opt_params> ")" "->" <type>
<opt_params>  ::= ""
              |   <params>
<params>      ::= <type>
              |   <params> "," <type>

К сожалению, эта грамматика - LR (2), а не LR (1). Эта проблема возникает с

( Integer   ) [ 42 ]
( Integer   ) -> Integer
          ^
          |
          +----------------- Lookahead

. На начальном этапе анализатор по-прежнему не знает, просматривает ли он (избыточно) тип в скобках или список параметров в типе функции. Он не будет знать об этом, пока не увидит следующий символ (который может быть концом ввода, в дополнение к двум опциям выше). В обоих случаях необходимо уменьшить Integer до <atomic_type>, а затем до <type>. Но тогда, в первом случае он может просто сдвинуть закрывающую скобку, а во втором случае он должен продолжать сокращаться, сначала до <params>, а затем до <opt_params>. Это конфликт сдвига-уменьшения. Конечно, это можно легко решить, заглянув в будущее еще одним токеном, но необходимость увидеть два токена в будущем - вот что делает грамматику LR (2).

К счастью, грамматики LR (k) всегда можно свести к грамматике LR (1). (Между прочим, это не относится к грамматике LL (k).) Это просто немного запутанно, потому что необходимо ввести немного избыточности. Мы делаем это, избегая необходимости уменьшать <type> до тех пор, пока не узнаем, что у нас есть список параметров, что означает, что нам нужно принять "(" <type> ")" без фиксации одного или другого анализа. Это приводит к следующему, когда к <function_type> было добавлено явно избыточное правило, а <opt_params> было изменено для принятия либо 0, либо хотя бы двух параметров:

<type>        ::= <atomic_type> <optional_array_size>
              |   <function_type>
<atomic_type> ::= <integer_type>
              |   <real_type>
              |   "(" <type> ")"
<function_type> ::= "(" <opt_params> ")" "->" <type>
              |   "(" <type> ")" "->" <type>
<opt_params>  ::= ""
              |   <params2>
<params2>     ::= <type> "," <type>
              |   <params2> "," <type>

Теперь я лично остановлюсь на этом. Существует множество генераторов синтаксического анализатора LR, и приведенная выше грамматика - LALR (1), и она все еще достаточно проста для чтения. Но возможно преобразовать его в грамматику LL (1), с небольшим количеством работы. (Я использовал инструмент преобразования грамматики, чтобы выполнить некоторые из этих преобразований.)

Удалять левую рекурсию, а затем левосторонний разложить грамматику просто:

# Not LL(1)
<type>          ::= <atomic_type> <opt_size>
                |   <function_type>
<opt_size>      ::= ""
                |   "[" integer "]"
<atomic_type>   ::= <integer_type>
                |   <real_type>
                |   "(" <type> ")"
<function_type> ::= "(" <fop>
<fop>           ::= <opt_params> ")" to <type>
                |   <type> ")" to <type>
<opt_params>    ::= ""
                |   <params2>
<params2>       ::= <type> "," <type> <params_tail>
<params_tail>   ::= "," <type> <params_tail>
                |   ""

Но это не так достаточно, потому что <function_type> и <atomic_type> могут начинаться с "(" <type>. И есть аналогичная проблема между продукцией для списка параметров. Чтобы избавиться от этих проблем, нам нужен еще один метод: развернуть нетерминалы на месте, чтобы перевести конфликты в один и тот же нетерминал, чтобы мы могли их оставить левыми. Как и в этом примере, это часто происходит за счет некоторого дублирования.

Расширяя <atomic_type>, <function_type> и <opt_params>, мы получаем:

<type>     ::= <integer_type> <opt_size>
           |   <real_type> <opt_size>
           |   "(" <type> ")" <opt_size>
           |   "(" ")" "->" <type>
           |   "(" <type> ")" "->" <type>
           |   "(" <type> "," <type> <params2> ")" "->" <type>
<opt_size> ::= "" 
           |   "[" INTEGER_LITERAL "]"
<params2>  ::= ""
           |   "," <type> <params2>

И затем мы можно с левым коэффициентом произвести

<type>     ::= <integer_type> <opt_size>
           |   <real_type> <opt_size>
           |   "(" <fop>
<fop>      ::= <type> <ftype>
           |   ")" "->" <type>
<ftype>    ::= ") <fcp>
           |   "," <type> <params2> ")" "->" <type>
<fcp>      ::= <opt_size>
           |   "->" <type>
<opt_size> ::= ""
           |   "[" INTEGER_LITERAL "]"
<params2>  ::= ""
           |   "," <type> <params2>

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

...