【问题标题】:Does Java has ambiguous syntax which needs more information about an identifier?Java 是否有模棱两可的语法,需要有关标识符的更多信息?
【发布时间】:2019-08-29 06:23:46
【问题描述】:

注意:这个问题不是关于“Java 没有指针”

在 C 语言中,代码identifier1 * identifier2 有两种可能的含义:

  1. 如果 identifier1 是一个类型,那么这可能是一个指针声明。
  2. 如果 identifier1 是一个变量,那么这可能是一个乘法语句。

问题是我在构建语法树时无法选择正确的产品。我检查了 Clang 的代码,似乎 Clang 必须将类型检查(通过使用符号表)放到解析阶段(如果我错了,请纠正我)。

然后我检查了javac(OpenJDK)的代码,似乎在解析阶段,没有涉及语义分析。解析器几乎可以使用令牌构建 AST。

所以我很好奇 Java 是否有同样的模棱两可的语法问题?如果解析器不知道标识符的类型,则无法选择正确的产生式的问题?

或者更笼统地说,Java 是否存在语法模棱两可的问题,以至于解析器无法在没有令牌流以外的其他信息的情况下选择产生式?

【问题讨论】:

  • 我不太明白这个问题:java没有指针,所以这里不能有歧义,因为*总是乘法。
  • 我不这么认为
  • @SanderDeDycker 我认为 OP 说的是一般性,而不仅仅是*。换句话说,是否有任何符号会在解析源代码时导致歧义,而这些符号只能通过了解上下文中使用的类型来解决。
  • 一些运算符重载,可能会短暂地混淆程序员,例如,var1 + var2 可能是 addition 如果 var1 = 1var2 = 2 或者它可能是 concatenation 如果var1 = "a"var2 = "b"。在混合大小写 - var1 = "a"var2 = 2 中,结果是一个字符串。但是,+ 运算符的结果基于所涉及的类型,并且这些类型在编译时是已知的,因此没有歧义。在对象Long + Long 的情况下,生成long。但是Long + null 不会编译,除非你指定它应该是Long 还是String
  • @VLAZ 但是 Java 和 C 都不支持运算符重载?

标签: java parsing semantics symbol-table


【解决方案1】:

我不认为 Java 有这个问题,因为 Java 是强类型的。 此外,Java 不支持指针,因此不会出现上述问题。 我希望这能回答你的问题。

【讨论】:

  • 这与强类型无关。这是关于模棱两可的语法。此外,歧义不仅限于 C 的指针语法。
【解决方案2】:

对于语言而言,标记化始终是上下文相关的。然而,Java 没有这么敏感的运算符。但是,您可以以这样的方式链接标记,它会产生歧义,但不仅仅是作为更大句法语句的一部分:

A < B 可以是public class A < B > { ... }if (A < B) { ... } 的一部分。 第一个是泛型​​类定义,第二个是比较。

这只是我的第一个例子,但我想还有更多。 但是,运算符通常定义非常狭窄,并且不能(如在 C/C++ 类语言中)被重载。此外,除了在 C/C++ 中,只有一个访问运算符(点:.),但有一个例外(从 Java 8 开始,双冒号 ::)。 在 C++ 中有一堆,所以不那么混乱。

关于 Java 是否总是在语法上可判定的具体问题: 是的。一个良好实现的编译器总是可以根据令牌流决定存在什么令牌。

【讨论】:

  • 在模板示例中,如果我进一步向前看,那么我可以检查这是模板声明还是比较语句,对吗?我可以这样想:在Java中,没有这样的歧义,即使得到了整个句子,解析器仍然无法选择产生式?
  • 您可以这样想:在 Java 中,至少据我所知,语法没有歧义。对于编译器来说,它应该始终是可决定的,一个标记代表什么样的语言元素。但是,如果编译器无法决定要调用的方法,则语义可能会产生歧义,因为两个方法的标头不明确。这可能发生在 lambda 表达式和 ::-operator 上。
【解决方案3】:

您的问题不容易回答;这取决于您拥有的生产规则。你说:

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

但这不是唯一可能的解析器!

看的时候带C

foo * bar;

它可以是一个名为bar 的指针,用于键入foo,或者foobar 的乘积可以解析为令牌流:

identifier_or_type ASTERISK identifier_or_type SEMICOLON

其余的取决于解析器的“业务逻辑”。所以这里在 parser 级别完全没有歧义,规则背后的逻辑使这两种情况有所不同。

【讨论】:

  • 我不这么认为,通过谈论解析,我的意思是构建一个 AST,它的所有节点都是确定的。关于你提到的,解析器仍然不知道该选择哪一个。
  • @reavenisadesk 这里只有一个制作,应该选择什么?
  • 不,有两个生产,&lt;pointer&gt; ::= * {&lt;type-qualifier&gt;}* {&lt;pointer&gt;}?&lt;multiplicative-expression&gt; ::= &lt;multiplicative-expression&gt; * &lt;cast-expression&gt;
  • @reavenisadesk 我的意思是,它必须成为解析器。上面答案中的规则是两种情况的明确解析规则,规则背后的逻辑使两种情况有所不同。这消除了解析器级别的歧义。
  • 不,如果你真的写了一个解析器,尤其是一个 ll(k),你不会把“id * id”作为一个不确定的节点,因为在更一般的情况下,一个指针都声明或者乘法语句可能有非终结符,需要进一步解析。我明白了,你只是指出“id * id”可以被解析,但我认为没有人会认为在解析阶段留下这个语句未知是可以的。
【解决方案4】:

foo.bar.bla.i 这样的表达式不能单独使用语法以有意义的方式进行解析。 foobarbla 中的每一个都可以是包名的一部分、静态变量(此变量不适用于 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;
    }
}

当静态变量bla 被注释掉时,这将打印2142

【讨论】:

  • 好点,但我认为这是一个范围优先级问题,无论有没有注释, foo.bar.bla 只是解析器级别的范围,对吧?
  • @reavenisadesk:我不明白你的意思。范围界定(如“这个“对x”的引用真的指向哪里?”)在解析之后出现(即已经构建了抽象语法树),并且确实是规避问题的一种解决方案.这正是问题的答案:如果没有其他信息(例如范围界定),您将无法正确解析。您不能使用如下规则声明 Java 语法:FullQualifiedClassName := (PackageName '.')? ClassName; PackageName := ID ('.' ID)*;.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-20
  • 1970-01-01
  • 2015-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多