Имеет ли Java неоднозначный синтаксис, для которого требуется больше информации об идентификаторе?

#java #parsing #semantics #symbol-table

#java #синтаксический анализ #семантика #таблица символов

Вопрос:

ОБРАТИТЕ внимание: этот вопрос не о «Java не имеет указателей»

На языке C код identifier1 * identifier2 неоднозначен для двух возможных значений:

  1. Если identifier1 является типом, то это может быть объявление указателя.
  2. Если identifier1 является переменной, то это может быть оператор multiply .

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

Затем я проверил код javac (OpenJDK), кажется, что на этапе синтаксического анализа семантический анализ не проводится. Анализатор может создать AST, едва используя токены.

Итак, мне любопытно, имеет ли Java такую же неоднозначную синтаксическую проблему? Проблема в том, что если анализатор не знает тип идентификатора, он не может выбрать правильное производство?

Или, в более общем смысле, имеет ли Java неоднозначный синтаксис, что анализатор не может выбрать продукт без другой информации, кроме потока токенов?

Комментарии:

1. Я не совсем понимаю вопрос: в java нет указателей, поэтому здесь не может быть двусмысленности, поскольку * всегда происходит умножение.

2. Я так не думаю

3. @SanderDeDycker Я думаю, что OP говорит в целом, а не только о. * Другими словами, существуют ли какие- либо символы, которые могут вызвать неоднозначность при анализе источника, которые могут быть решены только путем знания типов, используемых в контексте.

4. Некоторые операторы перегружены и могут ненадолго запутать программиста, например, var1 var2 может быть добавлением , если var1 = 1 и var2 = 2 или это может быть конкатенация , если var1 = "a" и var2 = "b" . В смешанном случае — var1 = "a" и var2 = 2 результатом является строка. Однако результат оператора основан на задействованных типах, и они известны во время компиляции, поэтому двусмысленности нет. В случае объектов Long Long выдает long . Но Long null не будет компилироваться, если вы не укажете, должно ли это быть Long или String

5. @VLAZ Но ни Java, ни C не поддерживают перегрузку оператора?

Ответ №1:

Токенизация всегда зависит от контекста для языков. Однако в Java нет операторов, которые являются настолько чувствительными. Однако вы можете связать токены таким образом, чтобы это создавало двусмысленность, но не только как часть более крупного синтаксического оператора:

A < B может быть частью обоих public class A < B > { ... } или if (A < B) { ... } . Первое — это определение общего класса, второе — сравнение.

Это только первый пример из верхней части моей шляпы, но я предполагаю, что их больше. Однако операторы обычно очень узко определены и не могут (как в C / C -подобных языках) быть перегружены. Кроме того, кроме C / C существует только один оператор доступа (точка: . ), за одним исключением (начиная с Java 8, двойное двоеточие :: ). В C их много, поэтому они гораздо менее хаотичны.

На конкретный вопрос о том, всегда ли Java синтаксически разрешима: Да. Хорошо реализованный компилятор всегда может решить, какой токен присутствует, в зависимости от потока токенов.

Комментарии:

1. В примере шаблона, если я загляну дальше, я смогу проверить, является ли это объявлением шаблона или оператором сравнения, верно? Могу ли я думать таким образом: в Java нет таких неоднозначных, которые даже получали целые предложения, синтаксический анализатор все еще не может выбрать производство?

2. Вы могли бы подумать так: в Java нет двусмысленности синтаксиса, по крайней мере, насколько мне известно. Для компилятора всегда должно быть разрешимо, какой языковой элемент представляет токен. Однако может возникнуть неоднозначность семантики, если компилятор не может выбрать вызываемый метод, поскольку два метода имеют неоднозначные заголовки. Это может произойти с лямбда-выражениями и :: -operator .

Ответ №2:

Я не думаю, что у Java есть эта проблема, поскольку Java строго типизирована. Кроме того, Java не поддерживает указатели, поэтому нет никаких шансов на вышеупомянутую проблему. Я надеюсь, что это ответ на ваш вопрос.

Комментарии:

1. Речь идет не о строгой типизации. Речь идет о неоднозначной грамматике. Более того, неоднозначность не ограничивается синтаксисом указателя C.

Ответ №3:

На ваш вопрос нелегко ответить; это зависит от ваших производственных правил. Вы говорите:

 there's two production:
<pointer> ::= * {<type-qualifier>}* {<pointer>}?
or
<multiplicative-expression> ::= <multiplicative-expression> * <cast-expression>
  

Но это не единственный возможный синтаксический анализатор!

С помощью C при просмотре

 foo * bar;
  

который может быть либо указателем, вызываемым bar на type foo , либо умножением foo на with bar , может быть проанализирован в потоке токенов:

 identifier_or_type ASTERISK identifier_or_type SEMICOLON
  

а остальное зависит от «бизнес-логики» анализатора. Таким образом, здесь вообще нет двусмысленности на уровне синтаксического анализатора, логика, лежащая в основе правила, определяет разницу между двумя случаями.

Комментарии:

1. Я так не думаю, говоря о синтаксическом анализе, я имею в виду создание AST, весь узел которого наверняка является определенным продуктом. О том, что вы упомянули, анализатор все еще не знает, какой из них выбрать.

2. @reavenisadesk Здесь есть только одно производство, из чего оно должно выбирать?

3. Нет, есть два производства, <pointer> ::= * {<type-qualifier>}* {<pointer>}? или <multiplicative-expression> ::= <multiplicative-expression> * <cast-expression>

4. @reavenisadesk Я хочу сказать, что это не обязательно должен быть анализатор. Правило в приведенном выше ответе является однозначным правилом синтаксического анализа для обоих случаев, логика, лежащая в основе правила, определяет разницу между двумя случаями. Это устраняет двусмысленность на уровне синтаксического анализатора.

5. Нет, если вы действительно пишете синтаксический анализатор, особенно ll (k), вы не будете просто указывать «id * id» как узел not for sure, потому что в более общей ситуации как объявление указателя, так и оператор multiply могут иметь нетерминалы и нуждаются в дальнейшем разборе. Я понял вас, вы просто указываете, что «id * id» может быть проанализирован, но я не думаю, что кто-то подумает, что оставить это утверждение неизвестным нормально на этапе синтаксического анализа.

Ответ №4:

Выражение, подобное foo.bar.bla.i , не может быть проанализировано осмысленным образом, используя только синтаксис. Каждый из foo bar и bla может быть либо частью имени пакета, статической переменной (к этой переменной не относится foo ), либо именем внутреннего класса.

Пример:

 public class Main {
    public static void main(String[] args) {
        System.out.println(foo.bar.bla.i);
    }
}
  

 package foo;
public class bar {

    public static class bla {
        public static int i = 42;
    }

//  public static NotBla bla = new NotBla();
    public static class NotBla {
        public static int i = 21;
    }
}
  

Это выведет либо 21 или 42 , когда статическая переменная bla закомментирована или нет.

Комментарии:

1. Хороший момент, но я думаю, что это проблема приоритета области, и независимо от того, с комментарием или без него, foo.bar.bla — это просто области на уровне синтаксического анализатора, верно?

2. @reavenisadesk: Я не понимаю вашу точку зрения. Определение области видимости (например, «Где эта» ссылка на x » действительно указывает на?») происходит после синтаксического анализа (т. Е. Построено Абстрактное синтаксическое дерево) и действительно является одним из решений для обхода проблемы. И это именно тот ответ на вопрос: вы не можете правильно анализировать без дополнительной информации (например, из области видимости). Вы не можете объявить Java-грамматику с такими правилами, как это: FullQualifiedClassName := (PackageName '.')? ClassName; PackageName := ID ('.' ID)*; .