например, в <2 он будет анализировать «a» как первичный элемент типа идентификатора, а затем немедленно найти синтаксис для универсального типа, проанализировать его и потерпеть неудачу, так как не может найти закрывающий тег </p>
Этот конкретный случай может быть решен с помощью обратного отслеживания или неограниченного прогнозирования. Как вы сказали, синтаксический анализатор в конечном итоге потерпит неудачу при интерпретации этого как универсального, поэтому, когда это произойдет, вы можете вернуться и проанализировать его как оператор отношения. Альтернативным вариантом было бы посмотреть вперед, увидев <
, чтобы проверить, следует ли за <
имена, разделенные запятыми, и за >
, и переходить в общее правило, только если это так.
Однако этот подход больше не работает, если обе интерпретации синтаксически верны (то есть синтаксис на самом деле неоднозначен). Одним из примеров этого может быть x<y>z
, который может быть либо объявлением переменной z
типа x<y>
, либо двумя сравнениями. Этот пример несколько проблематичен, так как последнее значение почти никогда не подразумевается, поэтому хорошо всегда интерпретировать его как первое (это происходит, например, в C #).
Теперь, если мы допустим выражения, это станет более сложным. Для x<y>z
достаточно просто сказать, что это никогда не должно интерпретироваться как сравнение двух, так как нет смысла сравнивать результат сравнения с чем-то другим (во многих языках использование реляционных операторов на логических значениях в любом случае является ошибкой типа). Но для чего-то вроде a<b<c>()
есть две интерпретации, которые могут быть обе действительны: либо a
является универсальной функцией, вызываемой с универсальным аргументом b<c
, либо b
является универсальной функцией с универсальным аргументом c
(и a
сравнивается с результатом вызова этой функции). На данный момент уже невозможно разрешить эту неоднозначность только с помощью синтаксических правил:
Чтобы поддержать это, вам нужно либо проверить, относится ли данное primary
к универсальной функции, и принять на основании этого различные решения по синтаксическому анализу, либо ваш синтаксический анализатор сгенерирует несколько деревьев в случае неоднозначностей, а затем выбрать исправить один на более позднем этапе. Первый вариант означает, что вашему анализатору необходимо отслеживать, какие универсальные функции в настоящее время определены (и находятся в области видимости), а затем переходить к универсальному правилу, только если данный primary
является именем одной из этих функций. Обратите внимание, что это станет намного сложнее, если вы позволите функциям быть определенными после их использования.
Таким образом, в итоге поддержка выражений в качестве общих аргументов требует, чтобы вы отслеживали, какие функции находятся в области действия при разборе, и использовали эту информацию для принятия ваших решений по синтаксическому анализу (то есть ваш синтаксический анализатор чувствителен к контексту) или для создания нескольких возможных AST. Без выражений вы можете оставить его свободным от контекста и однозначным, но для этого потребуется откат или произвольный просмотр (то есть LL(*)
).
Поскольку ни один из них не идеален, некоторые языки изменяют синтаксис для вызова обобщенных функций с явными параметрами типа, чтобы сделать его LL (1). Например:
- Java помещает общий список аргументов метода перед именем метода, то есть
obj.<T>foo()
вместо obj.foo<T>()
.
- Rust требует
::
перед общим списком аргументов: foo::<T>()
вместо foo<T>()
.
- Scala использует квадратные скобки для обобщений и ни для чего другого (для индексов массива используются скобки):
foo[T]()
вместо foo<T>()
.