ВНИМАНИЕ. Этот вопрос не о том, что «в Java нет указателей».
В языке C код identifier1 * identifier2
неоднозначен для двух возможных значений:
Проблема в том, что я не могу выбрать правильную продукцию при построении синтаксического дерева. Я проверил код Clang, и кажется, что Clang должен поставить проверку типов (с использованием таблицы символов) на фазу синтаксического анализа (поправьте меня, если я ошибаюсь).
Затем я проверил код javac (OpenJDK), кажется, что на этапе синтаксического анализа семантический анализ не используется. Парсер может построить AST, едва используя токены.
Поэтому мне любопытно, есть ли у Java такая же неоднозначная проблема с синтаксисом? Проблема в том, что если парсер не знает тип идентификатора, он не может выбрать правильную продукцию?
Или, более общий, имеет ли Java неоднозначный синтаксис, из-за которого синтаксический анализатор не может выбрать производство без другой информации, кроме потока токенов?
я так не думаю
@SanderDeDycker Я думаю, что ОП говорит в целом, а не только о *
. Другими словами, существуют ли символы Любые, которые могут вызвать неоднозначность при разборе источника, которую можно решить, только зная типы, используемые в контексте.
Некоторые операторы перегружены и могут ненадолго запутать программиста, например, var1 + var2
может быть добавление, если var1 = 1
и var2 = 2
, или может быть конкатенация, если var1 = "a"
и var2 = "b"
. В смешанном случае - var1 = "a"
и var2 = 2
результатом является строка. Однако результат оператора +
основан на задействованных типах, которые известны во время компиляции, поэтому двусмысленности нет. В случае объектов Long + Long
производит long
. Но Long + null
не будет компилироваться, если вы не укажете, должно ли это быть Long
или String
@VLAZ Но ни Java, ни C не поддерживают перегрузку операторов?
@Lundin +
означает две разные вещи. Меня учили, что это из-за того, что оператор перегружен. Не программистом, но он все еще выполняет несколько операций. Это неправильно?
@Lundin Java может не поддерживать перегрузку определяемых пользователем операторов, но +
перегружен, поскольку может означать добавление или конкатенацию строк, в зависимости от задействованных типов.
Если вы имеете в виду конкатенацию строк, которая на самом деле не перегружает, просто другой оператор с тем же символом.
@Lundin Разве это не совсем то, что такое перегрузка операторов? Кроме того, конкатенацию строк можно считать своего рода дополнением.
@Slaw Нет, перегрузка оператора означает предоставление определяемой пользователем функции, которая вызывается, когда в коде появляется соответствующий оператор. В противном случае C также имел бы «перегрузку оператора» повсюду.
@SanderDeDycker Я говорю об общем, а именно о том, что синтаксический анализатор должен получать информацию больше, чем просто значения идентификатора, чтобы выбрать производство.
@Lundin Хм, в случае +
на Java я могу не согласиться. Что касается реализации, в итоге создается StringBuilder
, добавляется ввод и создается окончательный String
. Так что в некотором смысле это является, вызывающий определяемую пользователем функцию (где пользователем являются разработчики JDK). Хотя это происходит только тогда, когда аргументы в конкатенации строк не все являются строковыми литералами; в противном случае я считаю, что компилятор вычисляет результат заранее.
Перегрузка определяется как «конкретный случай полиморфизма, когда разные операторы имеют разные реализации в зависимости от их аргументов». (википедия). Так что да, + в java перегружает. Но, во всяком случае, можно было бы подумать, что в C он тоже перегружен, поскольку добавление двух целых чисел реализовано иначе, чем добавление двух чисел с плавающей запятой. Так что простора для интерпретаций здесь предостаточно.
@Ctx Я согласен с этой интерпретацией. Если мы будем думать об этом как о функциях (long, long) -> long
и (int, int) -> int
, безусловно, они разные, даже если они называются одинаково. Точно так же organization.add(Department dep)
и ogranization.add(Person boardDirector)
также перегружены.
Я не думаю, что у Java есть эта проблема, поскольку Java строго типизирована. Кроме того, Java не поддерживает указатели, поэтому вышеуказанная проблема исключена. Надеюсь, это ответ на ваш вопрос.
Это не о строгой типизации. Речь идет о неоднозначной грамматике. Более того, неоднозначность не ограничивается синтаксисом указателя C.
Токенизация всегда зависит от контекста для языков. Однако в Java нет таких чувствительных операторов. Однако вы можете связать токены таким образом, чтобы это создавало двусмысленность, но не только как часть более крупного синтаксического оператора:
A < B
может быть частью как public class A < B > { ... }
, так и if (A < B) { ... }
.
Первое — это общее определение класса, второе — сравнение.
Это только первый пример из моей шляпы, но я предполагаю, что есть и другие.
Однако операторы обычно имеют очень узкое определение и не могут (как в языках, подобных C/C++) быть перегруженными. Кроме того, кроме C/C++, есть только один оператор доступа (точка: .
) с одним исключением (начиная с Java 8, двойное двоеточие ::
).
В C++ их куча, так что хаотичности гораздо меньше.
На конкретный вопрос о том, всегда ли Java синтаксически разрешима: да. Хорошо реализованный компилятор всегда может решить, какой токен присутствует, в зависимости от потока токенов.
В примере с шаблоном, если я загляну дальше, я смогу проверить, является ли это объявлением шаблона или оператором сравнения, верно? Могу ли я думать таким образом: в Java нет такой двусмысленности, которая даже получила бы целые предложения, синтаксический анализатор все равно не может выбрать продукцию?
Вы можете подумать так: в Java нет двусмысленности синтаксиса, по крайней мере, насколько мне известно. Для компилятора всегда должно быть понятно, какой элемент языка представляет токен. Однако может возникнуть неоднозначность семантики, если компилятор не может выбрать вызываемый метод, поскольку два метода имеют неоднозначные заголовки. Это может произойти с лямбда-выражениями и ::
-оператором.
На ваш вопрос нелегко ответить; это зависит от правил производства, которые у вас есть. Ты говоришь:
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, в котором все узлы наверняка являются определенным продуктом. О том, что вы упомянули, парсер все еще не знает, какой из них выбрать.
@reavenisadesk Здесь только одно производство, из чего ему выбирать?
Нет, есть два производства, <pointer> ::= * {<type-qualifier>}* {<pointer>}?
или <multiplicative-expression> ::= <multiplicative-expression> * <cast-expression>
@reavenisadesk Я хочу сказать, что это не имеют для парсера. Правило в приведенном выше ответе является однозначным правилом синтаксического анализа для обоих случаев, логика, стоящая за правилом, определяет разницу между двумя случаями. Это устраняет неоднозначность на уровне парсера.
Нет, если вы действительно пишете синтаксический анализатор, особенно ll(k), вы просто не сделаете "id * id" неверным узлом, потому что в более общей ситуации как объявление указателя, так и оператор умножения могут иметь не- терминалы и нуждаются в дальнейшем анализе. Я вас понял, вы просто указываете, что «id * id» можно разобрать, но я не думаю, что кто-то подумает, что оставить это утверждение неизвестным на этапе разбора — это нормально.
@reavenisadesk Под капотом вы генерируете несколько промежуточных представлений исходного кода на пути к коду ассемблера; Я не думаю, что это проблема здесь. Могут быть и другие варианты смягчения этого, но чистый синтаксический анализатор larr(1) не может различить две упомянутые вами семантики.
Это невозможно проанализировать с помощью larr. См. stackoverflow.com/a/1004737/2269707. GLR присутствует, чтобы исправить этот конфликт сокращения.
@reavenisadesk Это конфликтует, если у вас есть правила два; но нет, если у вас есть только одно правило, предложенное в ответе. Синтаксический анализатор GLR здесь не очень подходит, так как вы не можете определить правило на уровне синтаксиса, даже зная весь текст. Принимая во внимание другую семантику (то есть решая, является ли это идентификатором типа или переменной), вы можете решить это немедленно, поскольку определения типов или объявления переменных должны быть до их использования в C.
Такое выражение, как 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 — это просто области действия на уровне парсера, верно?
@reavenisadesk: я не понимаю твоей точки зрения. Область видимости (как в «Куда указывает эта ссылка на x
» В самом деле?») происходит после синтаксического анализа (т. е. построено абстрактное синтаксическое дерево) и действительно является одним из решений, позволяющих обойти проблему. И это именно ответ на вопрос: вы не можете правильно разобрать без дополнительной информации (например, из области видимости). Вы не можете объявить Java-грамматику с такими правилами: FullQualifiedClassName := (PackageName '.')? ClassName; PackageName := ID ('.' ID)*;
.
Я не совсем понимаю вопрос: в java нет указателей, поэтому здесь не может быть двусмысленности, так как
*
всегда умножение.