rtk-0.11: test-grammars/java.pg
grammar 'Java';
# ============================================================================
# LALR conflict inventory (happy: 17 shift/reduce, 0 reduce/reduce)
# ============================================================================
# Every remaining conflict is a shift/reduce whose default resolution (shift)
# is the correct Java reading. They fall into seven families; the items
# identify the automaton state (happy -i state numbers move with every
# grammar change, the items do not):
#
# 1. Dangling else - 1 state, 2 conflicts: 'else' and its splice token
# qq_OptElsePart, against reduce OptElsePart -> empty. Item:
# IfStatement -> 'if' '(' Expression ')' Statement . OptElsePart
# Shift binds the else part to the NEAREST 'if'; the reduce would close
# this if and hand the 'else' to an enclosing one. Shifting is the JLS
# 14.5 rule. See IfStatement.
#
# 2. catch/finally attach to the nearest try - 1 state, 4 conflicts:
# 'catch', 'finally' and their splice tokens qq_CatchList, qq_OptFinally,
# against reduce OptFinally -> empty. Items:
# TryStatement -> 'try' Statement CatchList . OptFinally
# CatchList -> CatchList . ('catch' '(' Parameter ')' Statement)
# In 'try try { } catch (E e) { } finally { }' each clause could extend
# the inner try (shift) or close it and attach to the outer one (reduce).
# Java attaches both to the nearest try.
#
# 3. Member id vs empty TypeParameters - 2 states (a type body after
# ModifierList, and the MemberDeclaration QQ entry), 1 conflict each on
# id, against reduce TypeParameters -> empty. Items:
# MemberDeclaration -> . id MemberAfterFirstId
# TypeParameters -> .
# In 'Foo bar(...)' the id either starts the non-generic alternative
# (shift) or follows an empty TypeParameters inside the generic one
# (reduce). No input is lost: id MemberAfterFirstId derives everything
# the empty-TypeParameters generic alternative would.
#
# 4. Greedy CompoundName '.' - 1 state, 1 conflict on '.', against reduce
# CompoundName -> id CompoundNameTail . Item:
# CompoundNameTail -> CompoundNameTail . ('.' id)
# 'a.b.c' extends the unreduced name instead of reducing 'a' early and
# parsing '.' as a field access; PostfixExpression '.' id still handles
# non-name bases ('f().x', 'this.x'), so no input is lost. The state
# also holds the class-literal/qualified-this/generic-invocation items
# anchored on the same 'id CompoundNameTail' prefix; they ride the SAME
# '.' shift, and the token after the '.' picks the branch (id extends
# the name, 'class'/'this'/'<' commit to one of them) - see the NOTE at
# PrimaryNoPostfix. Only expression positions carry those items: pure
# type positions (package, throws, extends, after 'new', ...) parse the
# name in a twin state without them and without the conflict.
#
# 5. Bracket-list shifts on '[' - 5 states, 1 conflict each:
# - after CompoundName in an expression, at statement start, and in a
# cast head (reduce: the call-parens option ('(' Arglist ')')? ->
# empty, i.e. commit to the bare name as a complete primary). Items:
# PrimaryNoPostfix -> CompoundName . '[' Expression ']'
# Type -> CompoundName . NonEmptyDims (and the
# CastExpression heads in the cast state)
# - after DimExprs and after NonEmptyDims in array creation (reduce:
# stop the dims list). Items:
# DimExprs -> DimExprs . ('[' Expression ']')
# NonEmptyDims -> NonEmptyDims . ('[' ']')
# Shifting '[' defers the type-vs-access decision by exactly one token
# (']' means a type's empty pair, an expression start means array
# access/sizing) or greedily extends the dims list; no input is lost.
# See the NOTEs at Dims and PrimaryNoPostfix.
#
# 6. '<' commits to type arguments - 2 states, 1 conflict each on '<':
# after CompoundName at statement start and in a cast head (reduce: bare-
# name primary, as in family 5). Standard for LALR Java parsers; see the
# NOTE at TypeArguments. Only the cast-head state loses real input
# ('(a < b)' as a parenthesized primary, see KNOWN LIMITATION at
# CastExpression); the other rejects only semantically invalid Java
# ('a < b;' as a bare statement-expression). The family's third state -
# pure type position, reduce Type -> CompoundName, whose lookahead held
# '<' only through 'x instanceof T < y' - vanished when
# RelationalExpression became non-associative: '<' no longer follows a
# complete relational operand. The same restructure lets ShiftOp be
# composed from single '>' tokens with NO conflict at all: RelationalOp
# '>' and ShiftOp '>' '>' share the shift of the first '>', and the next
# token splits them deterministically (another '>' extends the shift
# operator, an operand start reduces RelationalOp). See the NOTEs at
# RelationalExpression and ShiftOp.
#
# 7. QQ bootstrap dummy bracket - 1 state, 1 conflict. The quasi-quoter
# wraps a quote body in a per-rule dummy-token pair, and the whole-file
# entry 'Java -> DUMMY . Java DUMMY' lets a second opening DUMMY (shift)
# compete with deriving an empty CompilationUnit ((Package)? -> empty)
# right before the closing DUMMY (reduce). Only a completely empty
# [java| |] quote is affected (it fails to parse); any non-empty quote
# has a real token after the opening DUMMY.
#
# The cast-vs-paren decision one token AFTER ')' is conflict-free; see
# CastExpression.
# ============================================================================
Java = CompilationUnit ;
OptDocComment = (DocComment)? ;
TypeDeclaration =
OptDocComment ModifierList TypeDeclRest ;
ImportList = (ImportStatement)*;
CompilationUnit =
(Package)?
ImportList
(TypeDeclaration)? ;
Package =
'package' CompoundName ';' ;
ImportStatement =
'import' ('static')? ImportName ';' ;
# Import names have their own LEFT-recursive spelling instead of reusing
# CompoundName: the on-demand form would need CompoundName reduced on
# lookahead '.' (to then shift '.' '*'), but that reduce loses to the greedy
# name-extension shift (family 4), which made the star branch unreachable
# ('import a.b.*;' never parsed). Left recursion keeps both '.'
# continuations in one item set, so the token AFTER each '.' (id vs '*')
# picks the branch - no early commitment, no conflict.
ImportName =
ImportHead
| ImportHead '.' '*' ;
ImportHead =
id
| ImportHead '.' id ;
DocComment = doccomment;
# Annotations support
Annotation = '@' CompoundName ('(' AnnotationArguments? ')')? ;
AnnotationArguments = AnnotationElement (',' AnnotationElement)* ;
# Explicit alternatives (not (id '=')? Expression) so LALR keeps both items
# alive after shifting id; otherwise values starting with an identifier, e.g.
# @Retention(RetentionPolicy.RUNTIME), fail. ConditionalExpression is the JLS
# ElementValue level; full Expression would make @Foo(a = b) ambiguous.
AnnotationElement = id '=' ConditionalExpression | ConditionalExpression ;
AnnotationList = Annotation* ;
# Exactly ONE ModifierList is parsed per declaration, owned by the enclosing
# declaration rule (TypeDeclaration at the top level, FieldDeclaration inside
# a type body). The class/interface/enum/@interface rules must NOT start with
# their own nullable ModifierList: after the enclosing list, every token that
# can extend a ModifierList (the 10 modifier keywords, '@', and the splice
# tokens qq_Modifier/qq_Annotation/qq_ModifierList) used to conflict between
# extending the outer list and epsilon-starting the inner one - 14
# shift/reduce conflicts whose shift put all modifiers on the outer list and
# left the inner one always empty.
ModifierList = (Modifier | Annotation)* ;
# A supertype reference (JLS ClassOrInterfaceType): a possibly qualified
# name with optional type arguments - Comparable<A>, java.util.List<String>.
# The nullable TypeArguments directly after CompoundName is safe here: in
# extends/implements position no expression interpretation competes, so the
# epsilon-reduce of the empty TypeArguments (on ',', '{' or 'implements') is
# the state's only action on those tokens and '<' is a plain shift into
# NonEmptyTypeArguments. (Contrast with Type, which must spell out its
# NonEmptyTypeArguments alternative; see the NOTE there.)
ClassOrInterfaceType = CompoundName TypeArguments ;
ExtendsList = 'extends' ClassOrInterfaceType (',' ClassOrInterfaceType)* ;
ImplementsList = 'implements' ClassOrInterfaceType + ~',';
FieldDeclarationList = FieldDeclaration *;
ClassDeclaration =
'class' id TypeParameters
ExtendsList?
ImplementsList?
'{' FieldDeclarationList '}' ;
InterfaceDeclaration =
'interface' id TypeParameters
ExtendsList?
'{' FieldDeclarationList '}' ;
# Annotation declaration (@interface). The leading '@' shifts from the same
# state where '@' could extend the enclosing ModifierList with an Annotation;
# the NEXT token disambiguates ('interface' here vs id for an annotation), so
# the two uses of '@' never conflict.
AnnotationDeclaration =
'@' 'interface' id
'{' AnnotationTypeElementList '}' ;
AnnotationTypeElementList = AnnotationTypeElement* ;
# Annotation-type elements reuse FieldDeclaration; the default clause is
# handled by MemberRest (a dedicated alternative here would race its nullable
# ModifierList against FieldDeclaration's nullable OptDocComment → r/r).
AnnotationTypeElement = FieldDeclaration ;
# Enum declaration
EnumConstant = AnnotationList id ('(' Arglist ')')? ('{' FieldDeclarationList '}')? ;
EnumConstantList = EnumConstant (',' EnumConstant)* (',')? ;
EnumDeclaration =
'enum' id
ImplementsList?
'{' EnumConstantList (';' FieldDeclarationList)? '}' ;
# NOTE: MemberDeclaration unifies MethodDeclaration, VariableDeclaration, and ConstructorDeclaration
# to eliminate ambiguity. All three can start with an identifier:
# - Constructor: id '(' ...
# - Method: Type id '(' ... (where Type can be CompoundName, which is also id)
# - Field: Type id ';' or '=' ...
# By structuring alternatives carefully, we can distinguish them.
#
# NOTE: DocComment comes BEFORE ModifierList to match standard Java style:
# /** doc */ public int field;
# NOT: public /** doc */ int field;
# TypeDeclRest is everything of a type declaration AFTER its modifiers: the
# class/interface/enum/@interface keyword onwards. The ModifierList lives on
# the enclosing rule (TypeDeclaration at the top level, FieldDeclaration for
# nested types), see the NOTE at ModifierList.
TypeDeclRest =
ClassDeclaration
| InterfaceDeclaration
| EnumDeclaration
| AnnotationDeclaration ;
FieldDeclaration =
OptDocComment ModifierList (MemberDeclaration | TypeDeclRest | StaticInitializer)
| ';' ;
# Array brackets (JLS-style split): types and declarators may only contain
# EMPTY bracket pairs; sizing expressions are only allowed in array creation
# (see CreationExpression). Keeping expressions out of type brackets is what
# lets one token after 'name [' decide between a type (']') and an array
# access (expression start), so 'a[0] = 1;' and 'Foo[] x;' both parse.
#
# NOTE: RTK expands X* into a LEFT-recursive list with an empty base case, so
# a nullable Dims placed directly after CompoundName would demand an
# epsilon-reduce on '[' - exactly the early commitment we must avoid. Where
# the brackets compete with an expression interpretation (Type, casts) we
# therefore use NonEmptyDims, whose first '[' is a plain shift.
Dims = ('[' ']')* ;
NonEmptyDims = ('[' ']')+ ;
# MemberDeclaration unifies constructors, methods, and fields
# Strategy: Disambiguate early by checking what comes first
# - Primitive type keyword → definitely method/field
# - TypeParameters → definitely method (generic)
# - id → could be constructor or reference type method/field
MemberDeclaration =
PrimitiveTypeKeyword Dims id MemberRest # Primitive type method/field
| TypeParameters id MoreTypeSpecifier id MemberRest # Generic method with any type
| id MemberAfterFirstId ; # Constructor or reference type
# Primitive type keywords (from TypeSpecifier)
PrimitiveTypeKeyword =
'boolean' | 'byte' | 'char' | 'short' | 'int' | 'float' | 'long' | 'double' | 'void' ;
# After first id (without TypeParameters or primitive type), branch on next token:
MemberAfterFirstId =
'(' ParameterList? ')' ThrowsClause? StatementBlock # Constructor: id '(' ...
| MoreTypeSpecifier id MemberRest ; # Reference type: id ... id MemberRest
# MoreTypeSpecifier handles the rest of a type after the first id
# Could be: empty (simple type), '.' id (qualified), '<' ... '>' (generics), '[' ']' (array)
MoreTypeSpecifier =
'.' id MoreTypeSpecifier # Qualified name
| TypeArguments Dims ; # Generics + array brackets
# Throws clause for methods and constructors (JLS 8.4.6): 'throws' is a fresh
# keyword in follow position, so the optional clause never conflicts with the
# tokens that end a method header ('{', ';', 'default'). Exception types are
# CompoundNames: a generic class may not subclass Throwable, so type
# arguments cannot occur here.
ThrowsClause = 'throws' CompoundName (',' CompoundName)* ;
# MemberRest branches on what follows "Type id":
# - '(' indicates a method declaration
# - '[' or '=' or ',' or ';' indicates a variable declaration
# The optional 'default' clause before ';' is for annotation-type elements
# (int value() default 5;). Accepting it on ordinary methods too is
# parser-level tolerance; rejecting it there is a semantic check (javac's job).
MemberRest =
'(' ParameterList? ')' Dims ThrowsClause? ( StatementBlock | ('default' Expression)? ';' )
| Dims OptVariableInitializer MoreVariableDeclarators ';' ;
MoreVariableDeclarators = (',' VariableDeclarator)* ;
# Legacy: MethodDeclaration is now unified with VariableDeclaration in MemberDeclaration above
# MethodDeclaration =
# TypeParameters Type id
# '(' ParameterList ')' SquareBracketsList
# ( StatementBlock | ';' ) ;
# Legacy: ConstructorDeclaration is now unified in MemberDeclaration above
# ConstructorDeclaration =
# id '(' ParameterList ')'
# StatementBlock ;
StatementBlock = '{' StatementList '}' ;
VariableDeclaratorList = VariableDeclarator (',' VariableDeclarator)* ;
# VariableDeclaration is used for:
# 1. Local variable declarations inside method bodies (via Statement)
# 2. For loops (via ForStatement)
# Note: Field-level variable declarations use MemberDeclaration above
#
# The modified alternative carries a NON-EMPTY modifier list - deliberately
# not Parameter's nullable ParamModifierList: at statement start the parser
# already weighs "Type vs Expression" after an identifier, and a nullable
# prefix would force an epsilon-reduce decision BEFORE that identifier is
# shifted, sending 'Foo x;' down the expression path. 'final' and '@' cannot
# start an expression, so the modified alternative is keyword-anchored and
# the unmodified one keeps today's exact automaton.
VariableDeclaration =
Type VariableDeclaratorList
';'
| LocalModifierList1 Type VariableDeclaratorList ';' ;
# Local variable modifiers (JLS 14.4 VariableModifier), at least one.
LocalModifierList1 = ('final' | Annotation)+ ;
OptVariableInitializer = ('=' VariableInitializer)? ;
VariableDeclarator =
id Dims OptVariableInitializer ;
VariableInitializerList = (VariableInitializer (',' VariableInitializer)* (',')?)? ;
VariableInitializer =
Expression
| '{' VariableInitializerList '}' ;
StaticInitializer =
StatementBlock ;
ParameterList =
Parameter (',' Parameter)* ;
# Parameter modifiers (JLS 8.4.1 VariableModifier: 'final' and annotations).
# The NULLABLE prefix is safe here because parameter positions (after '(' or
# ',' in a parameter list, and in 'catch (...)') have no competing expression
# interpretation: the epsilon-reduce of the empty list on a type-start token
# is the state's only action. Do NOT reuse this at statement level, where a
# nullable prefix would break 'Foo x;' - see the NOTE at VariableDeclaration.
ParamModifierList = ('final' | Annotation)* ;
# '...' (varargs, JLS 8.4.1) is one lexer token: Alex's maximal munch prefers
# it over '.', and no float literal can follow '..'. That only the LAST
# parameter may be variadic is a semantic check (javac's job): the parser
# accepts the ellipsis on any parameter, including a catch parameter.
Parameter =
ParamModifierList Type ('...')? id Dims ;
StatementList = Statement *;
OptExpression = Expression? ;
OptId = id?;
# The 'super'/'this' call alternatives are explicit constructor invocations
# (JLS 8.8.7.1). They are allowed wherever a statement is: that they may
# only appear as the FIRST statement of a constructor body is a semantic
# check (javac's job). Both are conflict-free: 'this'/'super' directly
# followed by '(' had no parse at all before, and after shifting the
# keyword at statement start the next token decides ('(' starts the
# invocation; '.' and the operator/closer tokens reduce the keyword to the
# expression primary).
Statement =
VariableDeclaration
| 'return' OptExpression ';'
| Expression ';'
| StatementBlock
| IfStatement
| DoStatement
| WhileStatement
| ForStatement
| TryStatement
| SwitchStatement
| 'synchronized' '(' Expression ')' Statement
| 'throw' Expression ';'
| id ':' Statement
| 'break' OptId ';'
| 'continue' OptId ';'
| 'super' '(' Arglist ')' ';'
| 'this' '(' Arglist ')' ';'
| ';' ;
OptElsePart = ('else' Statement)? ;
# The then-branch is a full Statement, so a nested if needs no braces:
# 'if (a) if (b) f(); else g();' is valid Java. This is the classic
# dangling-else shift/reduce conflict (family 1 of the inventory at the top
# of this file); happy resolves it by shifting, i.e. 'else' binds to the
# NEAREST 'if' - exactly the JLS 14.5 rule. (A StatementWithoutIf then-
# branch used to exclude nested ifs from the then position: that dodged
# nothing - the conflict already existed via loop bodies like
# 'if (a) while (b) if (c) f(); else g();' - while rejecting the valid
# direct form above.)
IfStatement =
'if' '(' Expression ')' Statement
OptElsePart ;
DoStatement =
'do' Statement 'while' '(' Expression ')' ';' ;
WhileStatement =
'while' '(' Expression ')' Statement ;
ForStatement =
'for' '(' ( VariableDeclaration | ( Expression ';' ) | ';' )
OptExpression ';'
OptExpression
')' Statement ;
CatchList = ( 'catch' '(' Parameter ')' Statement)* ;
OptFinally = ('finally' Statement)?;
TryStatement =
'try' Statement
CatchList
OptFinally ;
SwitchCaseList = (( 'case' Expression ':' )
| ( 'default' ':' )
| Statement )*;
SwitchStatement =
'switch' '(' Expression ')' '{'
SwitchCaseList
'}' ;
# Expression Hierarchy
# Restructured to eliminate reduce/reduce conflicts by ensuring a single parse path
# through the operator precedence chain. Each expression level flows into the next
# without overlapping alternatives.
#
# UPDATED: All expression rules now use shared "Expression" type for QQ support
Expression : Expression = AssignmentExpression ;
Expression : AssignmentExpression =
ConditionalExpression (AssignmentOp AssignmentExpression)? ;
# COMMENTED OUT - CAUSES LEFT RECURSION:
# | Expression (/*NumericExpressionEnd
# | TestingExpressionEnd
# | LogicalExpressionEnd
# | BitExpressionEnd
# | */'(' Arglist ')'
# | '[' Expression ']'
# | '.' Expression
# | ',' Expression
# | 'instanceof' CompoundName )
AssignmentOp =
'='
| '+='
| '-='
| '*='
| '/='
| '|='
| '&='
| '^='
| '%='
| '<<='
| '>>='
| '>>>=' ;
Expression : ConditionalExpression = ConditionalOrExpression | ConditionalOrExpression '?' Expression ':' ConditionalExpression ;
Expression : ConditionalOrExpression = ConditionalAndExpression | ConditionalOrExpression '||' ConditionalAndExpression ;
Expression : ConditionalAndExpression = InclusiveOrEpression | ConditionalAndExpression '&&' InclusiveOrEpression ;
Expression : InclusiveOrEpression = ExclusiveOrExpression | InclusiveOrEpression '|' ExclusiveOrExpression;
Expression : ExclusiveOrExpression = AndExpression | ExclusiveOrExpression '^' AndExpression ;
Expression : AndExpression = EqualityExpression | AndExpression '&' EqualityExpression ;
EqualityOp = '==' | '!=' ;
Expression : EqualityExpression = RelationalExpression | EqualityExpression EqualityOp RelationalExpression ;
RelationalOp = '<' | '>' | '<=' | '>=' ;
# Non-associative on purpose: both operands are ShiftExpressions, not the
# JLS's left-recursive RelationalExpression. This is what lets ShiftOp below
# be composed from single '>' tokens: RelationalOp and ShiftOp are then
# expected from the SAME automaton state, so a lone '>' is shifted without
# committing and the NEXT token decides - a second '>' extends a shift
# operator, an operand start reduces RelationalOp '>'. With a left-recursive
# first operand that decision would instead be a reduce-vs-shift at the
# FIRST '>' (reduce to RelationalExpression to compare vs shift into
# ShiftOp), and happy's shift preference would make every plain 'a > b'
# unparseable. Cost: a chained relational ('a < b < c') is now a parse
# error instead of a semantic error - no loss for valid Java, where a
# relational result (boolean) admits no further relational operator.
# 'instanceof' keeps its left-recursive operand: the keyword follows a
# COMPLETE relational expression, so shifting it needs no early commitment.
Expression : RelationalExpression = ShiftExpression | ShiftExpression RelationalOp ShiftExpression | RelationalExpression 'instanceof' Type ;
# There are NO '>>'/'>>>' tokens: shift operators are composed in the parser
# from adjacent '>' tokens. RTK collects lexer literals from the rule text,
# so once no rule spells '>>'/'>>>', the input '>>' lexes as two '>' tokens:
# in 'Map<String, List<String>>' each '>' closes one TypeArguments level,
# while in 'a >> b' the parser assembles the operator from the two tokens
# (see the RelationalExpression NOTE above for why that is decidable).
# '>>=' and '>>>=' STAY single tokens in AssignmentOp: in valid Java those
# character sequences are always the assignment operator (a type-argument
# closer is never followed directly by '='). Trade-off: token adjacency is
# not checked, so a spaced 'a > > b' is (wrongly but harmlessly) accepted
# as 'a >> b'.
ShiftOp = '<<' | '>' '>' | '>' '>' '>' ;
Expression : ShiftExpression = AdditiveExpression | ShiftExpression ShiftOp AdditiveExpression ;
AdditiveOp = '+' | '-' ;
Expression : AdditiveExpression = MultiplicativeExpression | AdditiveExpression AdditiveOp MultiplicativeExpression ;
MultiplicativeOp = '*' | '/' | '%' ;
Expression : MultiplicativeExpression = UnaryExpression | MultiplicativeExpression MultiplicativeOp UnaryExpression ;
# JLS cast trick (JLS 19, CastExpression): no alternative needs a reduce
# before its first distinguishing token, so LALR(1) never has to decide
# "type or expression?" early:
# - a primitive keyword after '(' can only be a cast;
# - '(' CompoundName '<' commits to a generic cast (NonEmptyTypeArguments);
# - '(' CompoundName '[' is decided one token later: ']' means an array
# cast (NonEmptyDims), an expression start means an array access inside
# a parenthesized expression;
# - every other reference cast is parsed as '(' Expression ')' with the
# type-as-expression, e.g. (Foo) x or (java.util.List) x. Whether
# '(' Expression ')' is a cast or a parenthesized expression is decided
# one token after ')': an operand start means cast. '(a) + b' stays an
# addition precisely because '+'/'-' are excluded from
# UnaryExpressionNotPlusMinus (which is why the JLS has that nonterminal).
#
# KNOWN LIMITATION: '(a < b)' as a primary expression mis-commits on '<'
# (the shift prefers a generic cast '(a<...>) ...'), so e.g. 'x = (a < b);'
# is rejected. This is the standard trade-off of LALR Java parsers. In
# conditions (if/while/for) and argument lists there is no cast context, so
# 'if (a < b)' and 'f(a < b)' are unaffected.
#
# The '[' and '<' shifts inside the cast head are families 5 and 6 of the
# conflict inventory at the top of this file. The cast-vs-paren decision
# after ')' is conflict-FREE: operand-start tokens can never follow a
# complete parenthesized expression, so the lookahead sets are disjoint.
Expression : CastExpression =
'(' PrimitiveTypeKeyword Dims ')' UnaryExpression
| '(' CompoundName NonEmptyTypeArguments Dims ')' UnaryExpressionNotPlusMinus
| '(' CompoundName NonEmptyDims ')' UnaryExpressionNotPlusMinus
| '(' Expression ')' UnaryExpressionNotPlusMinus ;
# '~' and '!' live in UnaryExpressionNotPlusMinus (JLS 15.15), not here:
# keeping them in both derives ~x two ways, and they must stay in
# UnaryExpressionNotPlusMinus so reference-type casts can be followed by ~/!.
PrefixOp = '++' | '--' | '+' | '-' ;
PostfixOp = '++' | '--' ;
Expression : UnaryExpression =
PrefixOp UnaryExpression
| UnaryExpressionNotPlusMinus ;
Expression : UnaryExpressionNotPlusMinus =
PostfixExpression
| '~' UnaryExpression
| '!' UnaryExpression
| CastExpression ;
# The NonEmptyTypeArguments alternative is the explicit-type-argument call
# on a non-name base: this.<T>m(), f().<T>m(). After the shifted '.' the
# tokens id and '<' are distinct, so it adds no conflict. A NAME base
# (Collections.<String>emptyList()) never reaches it - the greedy family-4
# shift keeps the name unreduced - and parses through the PrimaryNoPostfix
# alternative anchored on 'id CompoundNameTail' instead; see the NOTE
# there.
Expression : PostfixExpression =
PrimaryNoPostfix
| PostfixExpression PostfixOp
| PostfixExpression '.' id
| PostfixExpression '.' id '(' Arglist ')'
| PostfixExpression '.' NonEmptyTypeArguments id '(' Arglist ')'
| PostfixExpression '[' Expression ']' ;
# The 'CompoundName '[' Expression ']'' alternative anchors array access on
# the unreduced name (JLS ArrayAccess: Name [ Expression ]). It must sit here
# and not only on PostfixExpression: reaching PostfixExpression first would
# reduce CompoundName on lookahead '[', committing against the type
# interpretation ('Foo[] x;', '(Foo[]) x') one token too early. With the
# anchored alternative both interpretations shift '[' and the NEXT token
# decides. 'a[0]' is still derivable through PostfixExpression too; the
# resulting shift/reduce conflicts on '[' prefer the shift, i.e. exactly this
# anchored production (the PostfixExpression production still applies to
# non-name bases such as 'f()[0]' or 'this.x[0]').
#
# The class-literal, qualified-this and explicit-type-argument-call
# alternatives anchor on the UNREDUCED name spelling 'id CompoundNameTail',
# NOT on CompoundName: the greedy name-extension shift (family 4) means a
# complete CompoundName is never reduced on lookahead '.', so a
# 'CompoundName '.' X' alternative could never be reached - the import-star
# lesson (see ImportName), expression edition. Spelling the shared
# CompoundNameTail keeps every '.' continuation in ONE item set, and the
# token after the shifted '.' decides: id extends the name, 'class'/'this'/
# '<' commit to one of these alternatives. The primitive class literal
# (int.class, void.class) is its own alternative; after the keyword the '.'
# shift is distinct from every Dims/declarator continuation. Array class
# literals (String[].class) are not covered (rare).
Expression : PrimaryNoPostfix =
Literal
| 'this'
| '(' Expression ')'
| CreationExpression
| CompoundName ('(' Arglist ')')?
| CompoundName '[' Expression ']'
| 'super' '.' id ('(' Arglist ')')?
| id CompoundNameTail '.' 'class'
| id CompoundNameTail '.' 'this'
| id CompoundNameTail '.' NonEmptyTypeArguments id '(' Arglist ')'
| PrimitiveTypeKeyword '.' 'class' ;
# Only array creation takes sizing expressions (DimExprs); trailing empty
# pairs allow 'new int[3][]'. Uses TypeSpecifier, not Type, so the dims are
# not consumed twice. 'new int[] {1,2}' (initializer syntax) is a separate
# feature (Phase 4g).
CreationExpression = 'new' TypeSpecifier ( '(' Arglist ')' | DimExprs (NonEmptyDims)? );
DimExprs = ('[' Expression ']')+ ;
Literal = integerLiteral | floatLiteral | 'true' | 'false' | char | string | 'null' ;
Arglist = (Expression (',' Expression)*)? ;
# Generics support
# NOTE: '<' is both the type-argument opener (List<String>) and the
# comparison operator (x < y). Wherever a CompoundName can be followed by
# either, the parser shifts '<' into NonEmptyTypeArguments (standard for
# Java parsers); see family 6 of the conflict inventory at the top of this
# file for the two states where this surfaces.
TypeArguments = NonEmptyTypeArguments? ;
# Non-optional variant: Type and CastExpression need the '<' to be a plain
# shift from CompoundName (an optional TypeArguments would interpose an
# epsilon-reduce and recreate the type-vs-expression conflict).
#
# The '<' '>' alternative is the diamond (JLS 15.9, new ArrayList<>()).
# After the shifted '<', the '>' and every type-argument start token are
# distinct, so it adds no conflict. "NonEmpty" still holds in the token
# sense - the rule always consumes the '<' '>' pair, it just may hold zero
# TypeArguments - which is exactly what keeps the '<' a plain shift. The
# diamond is thereby accepted wherever type arguments are (casts, extends,
# member types, ...); rejecting it outside 'new' is a semantic check
# (javac's job).
NonEmptyTypeArguments = '<' TypeArgument (',' TypeArgument)* '>' | '<' '>' ;
TypeArgument =
Type
| WildcardType ;
WildcardType =
'?'
| '?' 'extends' Type
| '?' 'super' Type ;
TypeParameters = ('<' TypeParameter (',' TypeParameter)* '>')? ;
TypeParameter = id ('extends' Type ('&' Type)*)? ;
# Type is spelled out by alternatives (instead of 'TypeSpecifier Dims') so
# that in positions where both a type and an expression may start (statement
# start, for-init) every alternative either shifts its next token directly
# from CompoundName ('<' for generics, '[' for array types) or reduces bare
# CompoundName only on lookahead 'id' (the declared variable's name), which
# no expression interpretation shares. This is what removes the
# PrimaryNoPostfix-vs-TypeSpecifier reduce/reduce conflict: the parser no
# longer has to pick type-or-expression while staring at '['.
Type =
PrimitiveTypeKeyword Dims
| CompoundName NonEmptyTypeArguments Dims
| CompoundName NonEmptyDims
| CompoundName ;
# Only used by CreationExpression nowadays (Type spells out its own
# alternatives, see above).
TypeSpecifier =
'boolean'
| 'byte'
| 'char'
| 'short'
| 'int'
| 'float'
| 'long'
| 'double'
| 'void'
| CompoundName TypeArguments ;
Modifier =
'public'
| 'private'
| 'protected'
| 'static'
| 'final'
| 'native'
| 'synchronized'
| 'abstract'
| 'threadsafe'
| 'transient' ;
# The tail of a dotted name, hoisted into a NAMED rule so that CompoundName
# and the PrimaryNoPostfix alternatives anchored on 'id CompoundNameTail'
# (class literal, qualified this, explicit-type-argument call) share ONE
# list nonterminal. The sharing is what makes those alternatives reachable:
# RTK extracts every inline sub-pattern into a FRESH nonterminal, and two
# distinct nullable lists starting after the same id would epsilon-reduce
# against each other (reduce/reduce) before the first '.' ever arrives.
# With one shared tail, all '.' continuations sit in one item set and the
# token after the '.' picks the branch - the same cure as ImportName's
# left-recursive spelling, applied to expression position.
CompoundNameTail = ('.' id)* ;
CompoundName =
id CompoundNameTail ;
# Integer literals (JLS 3.10.1): decimal/octal, hex (0x/0X), binary (0b/0B),
# underscores between digits, and l/L suffix. The decimal branch also covers
# octal since they are lexically identical.
integerLiteral =
( ( [0-9] ([0-9_]* [0-9])? )
| ( '0' [xX] [0-9a-fA-F] ([0-9a-fA-F_]* [0-9a-fA-F])? )
| ( '0' [bB] [01] ([01_]* [01])? ) )
[lL]? ;
# Float literals (JLS 3.10.2): underscores between digits, e/E exponent,
# f/F/d/D suffix. A bare exponent (1e5) or suffix (10f) also makes a float.
floatLiteral =
[0-9] ([0-9_]* [0-9])? '.' ([0-9] ([0-9_]* [0-9])?)? exponentPart? floatTypeSuffix?
| '.' [0-9] ([0-9_]* [0-9])? exponentPart? floatTypeSuffix?
| [0-9] ([0-9_]* [0-9])? exponentPart floatTypeSuffix?
| [0-9] ([0-9_]* [0-9])? floatTypeSuffix ;
exponentPart =
[eE] ( '+' | '-' )? [0-9] ([0-9_]* [0-9])? ;
floatTypeSuffix =
[fFdD] ;
# Character literal: handles simple chars and escape sequences
# Simple char: 'a', single-char escapes: '\n', '\t', multi-digit octal
# escapes: '\12', '\101', '\377' (JLS 3.10.6), and Unicode: '\u0041'
char = '\'' . '\''
| '\'' backslash . '\''
| '\'' backslash [0-7] [0-7] '\''
| '\'' backslash [0-3] [0-7] [0-7] '\''
| '\'' backslash 'u' [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] '\'' ;
# String literal: handles escape sequences like \", \\, \n, etc.
# Use negated class to match any char except quote, backslash, newline, CR
# Allow backslash followed by any char for escape sequences
string = [\"] ([^\"\\\n\r] | backslash .)* [\"] ;
# The literal-backslash class, as a charset macro. The grammar lexer reads
# '\]' as an escape pair inside [...], so a class ENDING in a backslash
# (like this one) mis-lexes whenever another ']' follows it on the same
# line; the macro pins the class at a line end once instead of constraining
# every use site. (It used to be spelled [\\x5C] at each use, leaking
# Alex's hex escape through the then-unescaped bracket translation -
# issue #95.)
@symmacro
backslash = [\\] ;
id = [a-zA-Z$_][a-zA-Z$_0-9]* ;
# Fixed: Split [*] [^/] into explicit cases for better Alex DFA generation
# This fixes blank line + {@link Class#method()} parsing issues
doccomment = '/**' ([\n] | [^*\n] | [*] [^/\n] | [*] [\n])* '*/';
# Whitespace and comments (ignored)
Ignore: ws = [ \t\n\r]+ ;
Ignore: comment = '//' [^\n]* '\n' ;
# Block comment must not match /** (which is doccomment)
# Same fix applied as doccomment
# Note: [^*] doesn't match newline in Alex, so we use ([^*\n] | [\n])
Ignore: blockComment = '/*' ([^*\n] | [\n]) ([\n] | [^*\n] | [*] [^/\n] | [*] [\n])* '*/' ;