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

ВНИМАНИЕ. Этот вопрос не о том, что «в Java нет указателей».

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

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

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

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

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

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

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

Sander De Dycker 08.04.2019 09:03

я так не думаю

Maurice Perry 08.04.2019 09:11

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

Slaw 08.04.2019 09:31

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

VLAZ 08.04.2019 09:32

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

Lundin 08.04.2019 09:32

@Lundin + означает две разные вещи. Меня учили, что это из-за того, что оператор перегружен. Не программистом, но он все еще выполняет несколько операций. Это неправильно?

VLAZ 08.04.2019 09:34

@Lundin Java может не поддерживать перегрузку определяемых пользователем операторов, но + перегружен, поскольку может означать добавление или конкатенацию строк, в зависимости от задействованных типов.

Slaw 08.04.2019 09:34

Если вы имеете в виду конкатенацию строк, которая на самом деле не перегружает, просто другой оператор с тем же символом.

Lundin 08.04.2019 09:36

@Lundin Разве это не совсем то, что такое перегрузка операторов? Кроме того, конкатенацию строк можно считать своего рода дополнением.

Slaw 08.04.2019 09:37

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

Lundin 08.04.2019 09:39

@SanderDeDycker Я говорю об общем, а именно о том, что синтаксический анализатор должен получать информацию больше, чем просто значения идентификатора, чтобы выбрать производство.

user2269707 08.04.2019 09:39

@Lundin Хм, в случае + на Java я могу не согласиться. Что касается реализации, в итоге создается StringBuilder, добавляется ввод и создается окончательный String. Так что в некотором смысле это является, вызывающий определяемую пользователем функцию (где пользователем являются разработчики JDK). Хотя это происходит только тогда, когда аргументы в конкатенации строк не все являются строковыми литералами; в противном случае я считаю, что компилятор вычисляет результат заранее.

Slaw 08.04.2019 09:45

Перегрузка определяется как «конкретный случай полиморфизма, когда разные операторы имеют разные реализации в зависимости от их аргументов». (википедия). Так что да, + в java перегружает. Но, во всяком случае, можно было бы подумать, что в C он тоже перегружен, поскольку добавление двух целых чисел реализовано иначе, чем добавление двух чисел с плавающей запятой. Так что простора для интерпретаций здесь предостаточно.

Ctx 08.04.2019 09:47

@Ctx Я согласен с этой интерпретацией. Если мы будем думать об этом как о функциях (long, long) -> long и (int, int) -> int, безусловно, они разные, даже если они называются одинаково. Точно так же organization.add(Department dep) и ogranization.add(Person boardDirector) также перегружены.

VLAZ 08.04.2019 09:55
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
14
246
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

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

Sapphire_Brick 19.01.2021 05:18
Ответ принят как подходящий

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

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

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

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

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

user2269707 08.04.2019 09:50

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

TreffnonX 08.04.2019 09:59

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

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

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

С C при взгляде на

foo * bar;

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

identifier_or_type ASTERISK identifier_or_type SEMICOLON

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

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

user2269707 08.04.2019 10:04

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

Ctx 08.04.2019 10:04

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

user2269707 08.04.2019 10:07

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

Ctx 08.04.2019 10:09

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

user2269707 08.04.2019 10:19

@reavenisadesk Под капотом вы генерируете несколько промежуточных представлений исходного кода на пути к коду ассемблера; Я не думаю, что это проблема здесь. Могут быть и другие варианты смягчения этого, но чистый синтаксический анализатор larr(1) не может различить две упомянутые вами семантики.

Ctx 08.04.2019 10:35

Это невозможно проанализировать с помощью larr. См. stackoverflow.com/a/1004737/2269707. GLR присутствует, чтобы исправить этот конфликт сокращения.

user2269707 08.04.2019 10:43

@reavenisadesk Это конфликтует, если у вас есть правила два; но нет, если у вас есть только одно правило, предложенное в ответе. Синтаксический анализатор GLR здесь не очень подходит, так как вы не можете определить правило на уровне синтаксиса, даже зная весь текст. Принимая во внимание другую семантику (то есть решая, является ли это идентификатором типа или переменной), вы можете решить это немедленно, поскольку определения типов или объявления переменных должны быть до их использования в C.

Ctx 08.04.2019 10:48

Такое выражение, как 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 закомментирована или нет.

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

user2269707 08.04.2019 11:04

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

A.H. 08.04.2019 17:59

Другие вопросы по теме