blob: c370ad8df9b953ca4e25358667f024ff5a542059 [file] [log] [blame]
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.turbine.parse;
import static com.google.turbine.parse.Token.COMMA;
import static com.google.turbine.parse.Token.IDENT;
import static com.google.turbine.parse.Token.INTERFACE;
import static com.google.turbine.parse.Token.LPAREN;
import static com.google.turbine.parse.Token.MINUS;
import static com.google.turbine.parse.Token.RPAREN;
import static com.google.turbine.parse.Token.SEMI;
import static com.google.turbine.tree.TurbineModifier.PROTECTED;
import static com.google.turbine.tree.TurbineModifier.PUBLIC;
import static com.google.turbine.tree.TurbineModifier.VARARGS;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.turbine.diag.SourceFile;
import com.google.turbine.diag.TurbineError;
import com.google.turbine.diag.TurbineError.ErrorKind;
import com.google.turbine.model.TurbineConstantTypeKind;
import com.google.turbine.model.TurbineTyKind;
import com.google.turbine.tree.Tree;
import com.google.turbine.tree.Tree.Anno;
import com.google.turbine.tree.Tree.ArrTy;
import com.google.turbine.tree.Tree.ClassTy;
import com.google.turbine.tree.Tree.CompUnit;
import com.google.turbine.tree.Tree.Expression;
import com.google.turbine.tree.Tree.Ident;
import com.google.turbine.tree.Tree.ImportDecl;
import com.google.turbine.tree.Tree.Kind;
import com.google.turbine.tree.Tree.MethDecl;
import com.google.turbine.tree.Tree.ModDecl;
import com.google.turbine.tree.Tree.ModDirective;
import com.google.turbine.tree.Tree.ModExports;
import com.google.turbine.tree.Tree.ModOpens;
import com.google.turbine.tree.Tree.ModProvides;
import com.google.turbine.tree.Tree.ModRequires;
import com.google.turbine.tree.Tree.ModUses;
import com.google.turbine.tree.Tree.PkgDecl;
import com.google.turbine.tree.Tree.PrimTy;
import com.google.turbine.tree.Tree.TyDecl;
import com.google.turbine.tree.Tree.TyParam;
import com.google.turbine.tree.Tree.Type;
import com.google.turbine.tree.Tree.VarDecl;
import com.google.turbine.tree.Tree.WildTy;
import com.google.turbine.tree.TurbineModifier;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import org.jspecify.nullness.Nullable;
/**
* A parser for the subset of Java required for header compilation.
*
* <p>See JLS 19: https://docs.oracle.com/javase/specs/jls/se8/html/jls-19.html
*/
public class Parser {
private static final String CTOR_NAME = "<init>";
private final Lexer lexer;
private Token token;
private int position;
public static CompUnit parse(String source) {
return parse(new SourceFile(null, source));
}
public static CompUnit parse(SourceFile source) {
return new Parser(new StreamLexer(new UnicodeEscapePreprocessor(source))).compilationUnit();
}
private Parser(Lexer lexer) {
this.lexer = lexer;
this.token = lexer.next();
}
public CompUnit compilationUnit() {
// TODO(cushon): consider enforcing package, import, and declaration order
// and make it bug-compatible with javac:
// http://mail.openjdk.java.net/pipermail/compiler-dev/2013-August/006968.html
Optional<PkgDecl> pkg = Optional.empty();
Optional<ModDecl> mod = Optional.empty();
EnumSet<TurbineModifier> access = EnumSet.noneOf(TurbineModifier.class);
ImmutableList.Builder<ImportDecl> imports = ImmutableList.builder();
ImmutableList.Builder<TyDecl> decls = ImmutableList.builder();
ImmutableList.Builder<Anno> annos = ImmutableList.builder();
while (true) {
switch (token) {
case PACKAGE:
{
next();
pkg = Optional.of(packageDeclaration(annos.build()));
annos = ImmutableList.builder();
break;
}
case IMPORT:
{
next();
ImportDecl i = importDeclaration();
if (i == null) {
continue;
}
imports.add(i);
break;
}
case PUBLIC:
next();
access.add(PUBLIC);
break;
case PROTECTED:
next();
access.add(PROTECTED);
break;
case PRIVATE:
next();
access.add(TurbineModifier.PRIVATE);
break;
case STATIC:
next();
access.add(TurbineModifier.STATIC);
break;
case ABSTRACT:
next();
access.add(TurbineModifier.ABSTRACT);
break;
case FINAL:
next();
access.add(TurbineModifier.FINAL);
break;
case STRICTFP:
next();
access.add(TurbineModifier.STRICTFP);
break;
case AT:
{
int pos = position;
next();
if (token == INTERFACE) {
decls.add(annotationDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
} else {
annos.add(annotation(pos));
}
break;
}
case CLASS:
decls.add(classDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
case INTERFACE:
decls.add(interfaceDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
case ENUM:
decls.add(enumDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
case EOF:
// TODO(cushon): check for dangling modifiers?
return new CompUnit(position, pkg, mod, imports.build(), decls.build(), lexer.source());
case SEMI:
// TODO(cushon): check for dangling modifiers?
next();
continue;
case IDENT:
{
Ident ident = ident();
if (ident.value().equals("record")) {
next();
decls.add(recordDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
}
if (ident.value().equals("sealed")) {
next();
access.add(TurbineModifier.SEALED);
break;
}
if (ident.value().equals("non")) {
int start = position;
next();
eatNonSealed(start);
next();
access.add(TurbineModifier.NON_SEALED);
break;
}
if (access.isEmpty()
&& (ident.value().equals("module") || ident.value().equals("open"))) {
boolean open = false;
if (ident.value().equals("open")) {
ident = eatIdent();
open = true;
}
if (!ident.value().equals("module")) {
throw error(token);
}
next();
mod = Optional.of(moduleDeclaration(open, annos.build()));
annos = ImmutableList.builder();
break;
}
}
// fall through
default:
throw error(token);
}
}
}
// Handle the hypenated pseudo-keyword 'non-sealed'.
//
// This will need to be updated to handle other hyphenated keywords if when/they are introduced.
private void eatNonSealed(int start) {
eat(Token.MINUS);
if (token != IDENT) {
throw error(token);
}
if (!ident().value().equals("sealed")) {
throw error(token);
}
if (position != start + "non-".length()) {
throw error(token);
}
}
private void next() {
token = lexer.next();
position = lexer.position();
}
private TyDecl recordDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
String javadoc = lexer.javadoc();
int pos = position;
Ident name = eatIdent();
ImmutableList<TyParam> typarams;
if (token == Token.LT) {
typarams = typarams();
} else {
typarams = ImmutableList.of();
}
ImmutableList.Builder<VarDecl> formals = ImmutableList.builder();
if (token == Token.LPAREN) {
next();
formalParams(formals, EnumSet.noneOf(TurbineModifier.class));
eat(Token.RPAREN);
}
ImmutableList.Builder<ClassTy> interfaces = ImmutableList.builder();
if (token == Token.IMPLEMENTS) {
next();
do {
interfaces.add(classty());
} while (maybe(Token.COMMA));
}
eat(Token.LBRACE);
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
return new TyDecl(
pos,
access,
annos,
name,
typarams,
Optional.<ClassTy>empty(),
interfaces.build(),
/* permits= */ ImmutableList.of(),
members,
formals.build(),
TurbineTyKind.RECORD,
javadoc);
}
private TyDecl interfaceDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
String javadoc = lexer.javadoc();
eat(Token.INTERFACE);
int pos = position;
Ident name = eatIdent();
ImmutableList<TyParam> typarams;
if (token == Token.LT) {
typarams = typarams();
} else {
typarams = ImmutableList.of();
}
ImmutableList.Builder<ClassTy> interfaces = ImmutableList.builder();
if (token == Token.EXTENDS) {
next();
do {
interfaces.add(classty());
} while (maybe(Token.COMMA));
}
ImmutableList.Builder<ClassTy> permits = ImmutableList.builder();
if (token == Token.IDENT) {
if (ident().value().equals("permits")) {
eat(Token.IDENT);
do {
permits.add(classty());
} while (maybe(Token.COMMA));
}
}
eat(Token.LBRACE);
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
return new TyDecl(
pos,
access,
annos,
name,
typarams,
Optional.<ClassTy>empty(),
interfaces.build(),
permits.build(),
members,
ImmutableList.of(),
TurbineTyKind.INTERFACE,
javadoc);
}
private TyDecl annotationDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
String javadoc = lexer.javadoc();
eat(Token.INTERFACE);
int pos = position;
Ident name = eatIdent();
eat(Token.LBRACE);
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
return new TyDecl(
pos,
access,
annos,
name,
ImmutableList.<TyParam>of(),
Optional.<ClassTy>empty(),
ImmutableList.<ClassTy>of(),
ImmutableList.of(),
members,
ImmutableList.of(),
TurbineTyKind.ANNOTATION,
javadoc);
}
private TyDecl enumDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
String javadoc = lexer.javadoc();
eat(Token.ENUM);
int pos = position;
Ident name = eatIdent();
ImmutableList.Builder<ClassTy> interfaces = ImmutableList.builder();
if (token == Token.IMPLEMENTS) {
next();
do {
interfaces.add(classty());
} while (maybe(Token.COMMA));
}
eat(Token.LBRACE);
ImmutableList<Tree> members =
ImmutableList.<Tree>builder().addAll(enumMembers(name)).addAll(classMembers()).build();
eat(Token.RBRACE);
return new TyDecl(
pos,
access,
annos,
name,
ImmutableList.<TyParam>of(),
Optional.<ClassTy>empty(),
interfaces.build(),
ImmutableList.of(),
members,
ImmutableList.of(),
TurbineTyKind.ENUM,
javadoc);
}
private String moduleName() {
return flatname('.', qualIdent());
}
private String packageName() {
return flatname('/', qualIdent());
}
private ModDecl moduleDeclaration(boolean open, ImmutableList<Anno> annos) {
int pos = position;
String moduleName = moduleName();
eat(Token.LBRACE);
ImmutableList.Builder<ModDirective> directives = ImmutableList.builder();
OUTER:
while (true) {
switch (token) {
case IDENT:
{
String ident = lexer.stringValue();
next();
switch (ident) {
case "requires":
directives.add(moduleRequires());
break;
case "exports":
directives.add(moduleExports());
break;
case "opens":
directives.add(moduleOpens());
break;
case "uses":
directives.add(moduleUses());
break;
case "provides":
directives.add(moduleProvides());
break;
default: // fall out
}
break;
}
case RBRACE:
break OUTER;
default:
throw error(token);
}
}
eat(Token.RBRACE);
return new ModDecl(pos, annos, open, moduleName, directives.build());
}
private static String flatname(char join, ImmutableList<Ident> idents) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Ident ident : idents) {
if (!first) {
sb.append(join);
}
sb.append(ident.value());
first = false;
}
return sb.toString();
}
private ModRequires moduleRequires() {
int pos = position;
EnumSet<TurbineModifier> access = EnumSet.noneOf(TurbineModifier.class);
while (true) {
if (token == Token.IDENT && lexer.stringValue().equals("transitive")) {
next();
access.add(TurbineModifier.TRANSITIVE);
break;
}
if (token == Token.STATIC) {
next();
access.add(TurbineModifier.STATIC);
break;
}
break;
}
String moduleName = moduleName();
eat(Token.SEMI);
return new ModRequires(pos, ImmutableSet.copyOf(access), moduleName);
}
private ModExports moduleExports() {
int pos = position;
String packageName = packageName();
ImmutableList.Builder<String> moduleNames = ImmutableList.builder();
if (lexer.stringValue().equals("to")) {
next();
do {
moduleNames.add(moduleName());
} while (maybe(Token.COMMA));
}
eat(Token.SEMI);
return new ModExports(pos, packageName, moduleNames.build());
}
private ModOpens moduleOpens() {
int pos = position;
String packageName = packageName();
ImmutableList.Builder<String> moduleNames = ImmutableList.builder();
if (lexer.stringValue().equals("to")) {
next();
do {
String moduleName = moduleName();
moduleNames.add(moduleName);
} while (maybe(Token.COMMA));
}
eat(Token.SEMI);
return new ModOpens(pos, packageName, moduleNames.build());
}
private ModUses moduleUses() {
int pos = position;
ImmutableList<Ident> uses = qualIdent();
eat(Token.SEMI);
return new ModUses(pos, uses);
}
private ModProvides moduleProvides() {
int pos = position;
ImmutableList<Ident> typeName = qualIdent();
if (!eatIdent().value().equals("with")) {
throw error(token);
}
ImmutableList.Builder<ImmutableList<Ident>> implNames = ImmutableList.builder();
do {
ImmutableList<Ident> implName = qualIdent();
implNames.add(implName);
} while (maybe(Token.COMMA));
eat(Token.SEMI);
return new ModProvides(pos, typeName, implNames.build());
}
private static final ImmutableSet<TurbineModifier> ENUM_CONSTANT_MODIFIERS =
ImmutableSet.of(
TurbineModifier.PUBLIC,
TurbineModifier.STATIC,
TurbineModifier.ACC_ENUM,
TurbineModifier.FINAL);
private ImmutableList<Tree> enumMembers(Ident enumName) {
ImmutableList.Builder<Tree> result = ImmutableList.builder();
ImmutableList.Builder<Anno> annos = ImmutableList.builder();
OUTER:
while (true) {
switch (token) {
case IDENT:
{
Ident name = eatIdent();
if (token == Token.LPAREN) {
dropParens();
}
// TODO(cushon): consider desugaring enum constants later
if (token == Token.LBRACE) {
dropBlocks();
}
maybe(Token.COMMA);
result.add(
new VarDecl(
position,
ENUM_CONSTANT_MODIFIERS,
annos.build(),
new ClassTy(
position,
Optional.<ClassTy>empty(),
enumName,
ImmutableList.<Type>of(),
ImmutableList.of()),
name,
Optional.<Expression>empty(),
null));
annos = ImmutableList.builder();
break;
}
case SEMI:
next();
annos = ImmutableList.builder();
break OUTER;
case RBRACE:
annos = ImmutableList.builder();
break OUTER;
case AT:
int pos = position;
next();
annos.add(annotation(pos));
break;
default:
throw error(token);
}
}
return result.build();
}
private TyDecl classDeclaration(EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
String javadoc = lexer.javadoc();
eat(Token.CLASS);
int pos = position;
Ident name = eatIdent();
ImmutableList<TyParam> tyParams = ImmutableList.of();
if (token == Token.LT) {
tyParams = typarams();
}
ClassTy xtnds = null;
if (token == Token.EXTENDS) {
next();
xtnds = classty();
}
ImmutableList.Builder<ClassTy> interfaces = ImmutableList.builder();
if (token == Token.IMPLEMENTS) {
next();
do {
interfaces.add(classty());
} while (maybe(Token.COMMA));
}
ImmutableList.Builder<ClassTy> permits = ImmutableList.builder();
if (token == Token.IDENT) {
if (ident().value().equals("permits")) {
eat(Token.IDENT);
do {
permits.add(classty());
} while (maybe(Token.COMMA));
}
}
switch (token) {
case LBRACE:
next();
break;
case EXTENDS:
throw error(ErrorKind.EXTENDS_AFTER_IMPLEMENTS);
default:
throw error(ErrorKind.EXPECTED_TOKEN, Token.LBRACE);
}
ImmutableList<Tree> members = classMembers();
eat(Token.RBRACE);
return new TyDecl(
pos,
access,
annos,
name,
tyParams,
Optional.ofNullable(xtnds),
interfaces.build(),
permits.build(),
members,
ImmutableList.of(),
TurbineTyKind.CLASS,
javadoc);
}
private ImmutableList<Tree> classMembers() {
ImmutableList.Builder<Tree> acc = ImmutableList.builder();
EnumSet<TurbineModifier> access = EnumSet.noneOf(TurbineModifier.class);
ImmutableList.Builder<Anno> annos = ImmutableList.builder();
while (true) {
switch (token) {
case PUBLIC:
next();
access.add(TurbineModifier.PUBLIC);
break;
case PROTECTED:
next();
access.add(TurbineModifier.PROTECTED);
break;
case PRIVATE:
next();
access.add(TurbineModifier.PRIVATE);
break;
case STATIC:
next();
access.add(TurbineModifier.STATIC);
break;
case ABSTRACT:
next();
access.add(TurbineModifier.ABSTRACT);
break;
case FINAL:
next();
access.add(TurbineModifier.FINAL);
break;
case NATIVE:
next();
access.add(TurbineModifier.NATIVE);
break;
case SYNCHRONIZED:
next();
access.add(TurbineModifier.SYNCHRONIZED);
break;
case TRANSIENT:
next();
access.add(TurbineModifier.TRANSIENT);
break;
case VOLATILE:
next();
access.add(TurbineModifier.VOLATILE);
break;
case STRICTFP:
next();
access.add(TurbineModifier.STRICTFP);
break;
case DEFAULT:
next();
access.add(TurbineModifier.DEFAULT);
break;
case AT:
{
// TODO(cushon): de-dup with top-level parsing
int pos = position;
next();
if (token == INTERFACE) {
acc.add(annotationDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
} else {
annos.add(annotation(pos));
}
break;
}
case IDENT:
Ident ident = ident();
if (ident.value().equals("non")) {
int pos = position;
next();
if (token != MINUS) {
acc.addAll(member(access, annos.build(), ImmutableList.of(), pos, ident));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
} else {
eatNonSealed(pos);
next();
access.add(TurbineModifier.NON_SEALED);
}
break;
}
if (ident.value().equals("record")) {
eat(IDENT);
acc.add(recordDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
}
// fall through
case BOOLEAN:
case BYTE:
case SHORT:
case INT:
case LONG:
case CHAR:
case DOUBLE:
case FLOAT:
case VOID:
case LT:
acc.addAll(classMember(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
case LBRACE:
dropBlocks();
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
case CLASS:
acc.add(classDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
case INTERFACE:
acc.add(interfaceDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
case ENUM:
acc.add(enumDeclaration(access, annos.build()));
access = EnumSet.noneOf(TurbineModifier.class);
annos = ImmutableList.builder();
break;
case RBRACE:
return acc.build();
case SEMI:
next();
continue;
default:
throw error(token);
}
}
}
private ImmutableList<Tree> classMember(
EnumSet<TurbineModifier> access, ImmutableList<Anno> annos) {
ImmutableList<TyParam> typaram = ImmutableList.of();
Type result;
Ident name;
if (token == Token.LT) {
typaram = typarams();
}
if (token == Token.AT) {
annos = ImmutableList.<Anno>builder().addAll(annos).addAll(maybeAnnos()).build();
}
switch (token) {
case VOID:
{
result = new Tree.VoidTy(position);
next();
int pos = position;
name = eatIdent();
return memberRest(pos, access, annos, typaram, result, name);
}
case BOOLEAN:
case BYTE:
case SHORT:
case INT:
case LONG:
case CHAR:
case DOUBLE:
case FLOAT:
{
result = referenceType(ImmutableList.of());
int pos = position;
name = eatIdent();
return memberRest(pos, access, annos, typaram, result, name);
}
case IDENT:
int pos = position;
Ident ident = eatIdent();
return member(access, annos, typaram, pos, ident);
default:
throw error(token);
}
}
private ImmutableList<Tree> member(
EnumSet<TurbineModifier> access,
ImmutableList<Anno> annos,
ImmutableList<TyParam> typaram,
int pos,
Ident ident) {
Type result;
Ident name;
switch (token) {
case LPAREN:
{
name = ident;
return ImmutableList.of(methodRest(pos, access, annos, typaram, null, name));
}
case LBRACE:
{
dropBlocks();
name = new Ident(position, CTOR_NAME);
String javadoc = lexer.javadoc();
access.add(TurbineModifier.COMPACT_CTOR);
return ImmutableList.<Tree>of(
new MethDecl(
pos,
access,
annos,
typaram,
/* ret= */ Optional.empty(),
name,
/* params= */ ImmutableList.of(),
/* exntys= */ ImmutableList.of(),
/* defaultValue= */ Optional.empty(),
javadoc));
}
case IDENT:
{
result =
new ClassTy(
position,
Optional.<ClassTy>empty(),
ident,
ImmutableList.<Type>of(),
ImmutableList.of());
pos = position;
name = eatIdent();
return memberRest(pos, access, annos, typaram, result, name);
}
case AT:
case LBRACK:
{
result =
new ClassTy(
position,
Optional.<ClassTy>empty(),
ident,
ImmutableList.<Type>of(),
ImmutableList.of());
result = maybeDims(maybeAnnos(), result);
break;
}
case LT:
{
result =
new ClassTy(position, Optional.<ClassTy>empty(), ident, tyargs(), ImmutableList.of());
result = maybeDims(maybeAnnos(), result);
break;
}
case DOT:
result =
new ClassTy(
position,
Optional.<ClassTy>empty(),
ident,
ImmutableList.<Type>of(),
ImmutableList.of());
break;
default:
throw error(token);
}
if (result == null) {
throw error(token);
}
if (token == Token.DOT) {
next();
if (!result.kind().equals(Kind.CLASS_TY)) {
throw error(token);
}
result = classty((ClassTy) result);
}
result = maybeDims(maybeAnnos(), result);
pos = position;
name = eatIdent();
switch (token) {
case LPAREN:
return ImmutableList.of(methodRest(pos, access, annos, typaram, result, name));
case LBRACK:
case SEMI:
case ASSIGN:
case COMMA:
{
if (!typaram.isEmpty()) {
throw error(ErrorKind.UNEXPECTED_TYPE_PARAMETER, typaram);
}
return fieldRest(pos, access, annos, result, name);
}
default:
throw error(token);
}
}
private ImmutableList<Anno> maybeAnnos() {
if (token != Token.AT) {
return ImmutableList.of();
}
ImmutableList.Builder<Anno> builder = ImmutableList.builder();
while (token == Token.AT) {
int pos = position;
next();
builder.add(annotation(pos));
}
return builder.build();
}
private ImmutableList<Tree> memberRest(
int pos,
EnumSet<TurbineModifier> access,
ImmutableList<Anno> annos,
ImmutableList<TyParam> typaram,
Type result,
Ident name) {
switch (token) {
case ASSIGN:
case AT:
case COMMA:
case LBRACK:
case SEMI:
{
if (!typaram.isEmpty()) {
throw error(ErrorKind.UNEXPECTED_TYPE_PARAMETER, typaram);
}
return fieldRest(pos, access, annos, result, name);
}
case LPAREN:
return ImmutableList.of(methodRest(pos, access, annos, typaram, result, name));
default:
throw error(token);
}
}
private ImmutableList<Tree> fieldRest(
int pos,
EnumSet<TurbineModifier> access,
ImmutableList<Anno> annos,
Type baseTy,
Ident name) {
String javadoc = lexer.javadoc();
ImmutableList.Builder<Tree> result = ImmutableList.builder();
VariableInitializerParser initializerParser = new VariableInitializerParser(token, lexer);
List<List<SavedToken>> bits = initializerParser.parseInitializers();
token = initializerParser.token;
boolean first = true;
int expressionStart = pos;
for (List<SavedToken> bit : bits) {
IteratorLexer lexer = new IteratorLexer(this.lexer.source(), bit.iterator());
Parser parser = new Parser(lexer);
if (first) {
first = false;
} else {
name = parser.eatIdent();
}
Type ty = baseTy;
ty = parser.extraDims(ty);
// TODO(cushon): skip more fields that are definitely non-const
ConstExpressionParser constExpressionParser =
new ConstExpressionParser(lexer, lexer.next(), lexer.position());
expressionStart = lexer.position();
Expression init = constExpressionParser.expression();
if (init != null && init.kind() == Tree.Kind.ARRAY_INIT) {
init = null;
}
result.add(new VarDecl(pos, access, annos, ty, name, Optional.ofNullable(init), javadoc));
}
if (token != SEMI) {
throw TurbineError.format(lexer.source(), expressionStart, ErrorKind.UNTERMINATED_EXPRESSION);
}
eat(Token.SEMI);
return result.build();
}
private Tree methodRest(
int pos,
EnumSet<TurbineModifier> access,
ImmutableList<Anno> annos,
ImmutableList<TyParam> typaram,
Type result,
Ident name) {
String javadoc = lexer.javadoc();
eat(Token.LPAREN);
ImmutableList.Builder<VarDecl> formals = ImmutableList.builder();
formalParams(formals, access);
eat(Token.RPAREN);
result = extraDims(result);
ImmutableList.Builder<ClassTy> exceptions = ImmutableList.builder();
if (token == Token.THROWS) {
next();
exceptions.addAll(exceptions());
}
Tree defaultValue = null;
switch (token) {
case SEMI:
next();
break;
case LBRACE:
dropBlocks();
break;
case DEFAULT:
{
ConstExpressionParser cparser =
new ConstExpressionParser(lexer, lexer.next(), lexer.position());
Tree expr = cparser.expression();
token = cparser.token;
if (expr == null && token == Token.AT) {
int annoPos = position;
next();
expr = annotation(annoPos);
}
if (expr == null) {
throw error(token);
}
defaultValue = expr;
eat(Token.SEMI);
break;
}
default:
throw error(token);
}
if (result == null) {
name = new Ident(position, CTOR_NAME);
}
return new MethDecl(
pos,
access,
annos,
typaram,
Optional.<Tree>ofNullable(result),
name,
formals.build(),
exceptions.build(),
Optional.ofNullable(defaultValue),
javadoc);
}
/**
* Given a base {@code type} and some number of {@code extra} c-style array dimension specifiers,
* construct a new array type.
*
* <p>For reasons that are unclear from the spec, {@code int @A [] x []} is equivalent to {@code
* int [] @A [] x}, not {@code int @A [] [] x}.
*/
private Type extraDims(Type ty) {
ImmutableList<Anno> annos = maybeAnnos();
if (!annos.isEmpty() && token != Token.LBRACK) {
// orphaned type annotations
throw error(token);
}
Deque<ImmutableList<Anno>> extra = new ArrayDeque<>();
while (maybe(Token.LBRACK)) {
eat(Token.RBRACK);
extra.push(annos);
annos = maybeAnnos();
}
ty = extraDims(ty, extra);
return ty;
}
private Type extraDims(Type type, Deque<ImmutableList<Anno>> extra) {
if (extra.isEmpty()) {
return type;
}
if (type == null) {
// trailing dims without a type, e.g. for a constructor declaration
throw error(token);
}
if (type.kind() == Kind.ARR_TY) {
ArrTy arrTy = (ArrTy) type;
return new ArrTy(arrTy.position(), arrTy.annos(), extraDims(arrTy.elem(), extra));
}
return new ArrTy(type.position(), extra.pop(), extraDims(type, extra));
}
private ImmutableList<ClassTy> exceptions() {
ImmutableList.Builder<ClassTy> result = ImmutableList.builder();
result.add(classty());
while (maybe(Token.COMMA)) {
result.add(classty());
}
return result.build();
}
private void formalParams(
ImmutableList.Builder<VarDecl> builder, EnumSet<TurbineModifier> access) {
while (token != Token.RPAREN) {
VarDecl formal = formalParam();
builder.add(formal);
if (formal.mods().contains(TurbineModifier.VARARGS)) {
access.add(TurbineModifier.VARARGS);
}
if (token != Token.COMMA) {
break;
}
next();
}
}
private VarDecl formalParam() {
ImmutableList.Builder<Anno> annos = ImmutableList.builder();
EnumSet<TurbineModifier> access = modifiersAndAnnotations(annos);
Type ty = referenceTypeWithoutDims(ImmutableList.of());
ImmutableList<Anno> typeAnnos = maybeAnnos();
OUTER:
while (true) {
switch (token) {
case LBRACK:
next();
eat(Token.RBRACK);
ty = new ArrTy(position, typeAnnos, ty);
typeAnnos = maybeAnnos();
break;
case ELLIPSIS:
next();
access.add(VARARGS);
ty = new ArrTy(position, typeAnnos, ty);
typeAnnos = ImmutableList.of();
break OUTER;
default:
break OUTER;
}
}
if (!typeAnnos.isEmpty()) {
throw error(token);
}
// the parameter name is `this` for receiver parameters, and a qualified this expression
// for inner classes
Ident name = identOrThis();
while (token == Token.DOT) {
eat(Token.DOT);
// Overwrite everything up to the terminal 'this' for inner classes; we don't need it
name = identOrThis();
}
ty = extraDims(ty);
return new VarDecl(
position, access, annos.build(), ty, name, Optional.<Expression>empty(), null);
}
private Ident identOrThis() {
switch (token) {
case IDENT:
return eatIdent();
case THIS:
int position = lexer.position();
eat(Token.THIS);
return new Ident(position, "this");
default:
throw error(token);
}
}
private void dropParens() {
eat(Token.LPAREN);
int depth = 1;
while (depth > 0) {
switch (token) {
case RPAREN:
depth--;
break;
case LPAREN:
depth++;
break;
case EOF:
throw error(ErrorKind.UNEXPECTED_EOF);
default:
break;
}
next();
}
}
private void dropBlocks() {
eat(Token.LBRACE);
int depth = 1;
while (depth > 0) {
switch (token) {
case RBRACE:
depth--;
break;
case LBRACE:
depth++;
break;
case EOF:
throw error(ErrorKind.UNEXPECTED_EOF);
default:
break;
}
next();
}
}
private ImmutableList<TyParam> typarams() {
ImmutableList.Builder<TyParam> acc = ImmutableList.builder();
eat(Token.LT);
OUTER:
while (true) {
ImmutableList<Anno> annotations = maybeAnnos();
int pos = position;
Ident name = eatIdent();
ImmutableList<Tree> bounds = ImmutableList.of();
if (token == Token.EXTENDS) {
next();
bounds = tybounds();
}
acc.add(new TyParam(pos, name, bounds, annotations));
switch (token) {
case COMMA:
eat(Token.COMMA);
continue;
case GT:
next();
break OUTER;
default:
throw error(token);
}
}
return acc.build();
}
private ImmutableList<Tree> tybounds() {
ImmutableList.Builder<Tree> acc = ImmutableList.builder();
do {
acc.add(classty());
} while (maybe(Token.AND));
return acc.build();
}
private ClassTy classty() {
return classty(null);
}
private ClassTy classty(ClassTy ty) {
return classty(ty, null);
}
private ClassTy classty(ClassTy ty, @Nullable ImmutableList<Anno> typeAnnos) {
int pos = position;
do {
if (typeAnnos == null) {
typeAnnos = maybeAnnos();
}
Ident name = eatIdent();
ImmutableList<Type> tyargs = ImmutableList.of();
if (token == Token.LT) {
tyargs = tyargs();
}
ty = new ClassTy(pos, Optional.ofNullable(ty), name, tyargs, typeAnnos);
typeAnnos = null;
} while (maybe(Token.DOT));
return ty;
}
private ImmutableList<Type> tyargs() {
ImmutableList.Builder<Type> acc = ImmutableList.builder();
eat(Token.LT);
OUTER:
do {
ImmutableList<Anno> typeAnnos = maybeAnnos();
switch (token) {
case COND:
{
next();
switch (token) {
case EXTENDS:
next();
Type upper = referenceType(maybeAnnos());
acc.add(
new WildTy(position, typeAnnos, Optional.of(upper), Optional.<Type>empty()));
break;
case SUPER:
next();
Type lower = referenceType(maybeAnnos());
acc.add(
new WildTy(position, typeAnnos, Optional.<Type>empty(), Optional.of(lower)));
break;
case COMMA:
acc.add(
new WildTy(
position, typeAnnos, Optional.<Type>empty(), Optional.<Type>empty()));
continue OUTER;
case GT:
case GTGT:
case GTGTGT:
acc.add(
new WildTy(
position, typeAnnos, Optional.<Type>empty(), Optional.<Type>empty()));
break OUTER;
default:
throw error(token);
}
break;
}
case IDENT:
case BOOLEAN:
case BYTE:
case SHORT:
case INT:
case LONG:
case CHAR:
case DOUBLE:
case FLOAT:
acc.add(referenceType(typeAnnos));
break;
default:
throw error(token);
}
} while (maybe(Token.COMMA));
switch (token) {
case GT:
next();
break;
case GTGT:
token = Token.GT;
break;
case GTGTGT:
token = Token.GTGT;
break;
default:
throw error(token);
}
return acc.build();
}
private Type referenceTypeWithoutDims(ImmutableList<Anno> typeAnnos) {
switch (token) {
case IDENT:
return classty(null, typeAnnos);
case BOOLEAN:
next();
return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.BOOLEAN);
case BYTE:
next();
return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.BYTE);
case SHORT:
next();
return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.SHORT);
case INT:
next();
return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.INT);
case LONG:
next();
return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.LONG);
case CHAR:
next();
return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.CHAR);
case DOUBLE:
next();
return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.DOUBLE);
case FLOAT:
next();
return new PrimTy(position, typeAnnos, TurbineConstantTypeKind.FLOAT);
default:
throw error(token);
}
}
private Type referenceType(ImmutableList<Anno> typeAnnos) {
Type ty = referenceTypeWithoutDims(typeAnnos);
return maybeDims(maybeAnnos(), ty);
}
private Type maybeDims(ImmutableList<Anno> typeAnnos, Type ty) {
while (maybe(Token.LBRACK)) {
eat(Token.RBRACK);
ty = new ArrTy(position, typeAnnos, ty);
typeAnnos = maybeAnnos();
}
return ty;
}
private EnumSet<TurbineModifier> modifiersAndAnnotations(ImmutableList.Builder<Anno> annos) {
EnumSet<TurbineModifier> access = EnumSet.noneOf(TurbineModifier.class);
while (true) {
switch (token) {
case PUBLIC:
next();
access.add(TurbineModifier.PUBLIC);
break;
case PROTECTED:
next();
access.add(TurbineModifier.PROTECTED);
break;
case PRIVATE:
next();
access.add(TurbineModifier.PRIVATE);
break;
case STATIC:
next();
access.add(TurbineModifier.STATIC);
break;
case ABSTRACT:
next();
access.add(TurbineModifier.ABSTRACT);
break;
case FINAL:
next();
access.add(TurbineModifier.FINAL);
break;
case NATIVE:
next();
access.add(TurbineModifier.NATIVE);
break;
case SYNCHRONIZED:
next();
access.add(TurbineModifier.SYNCHRONIZED);
break;
case TRANSIENT:
next();
access.add(TurbineModifier.TRANSIENT);
break;
case VOLATILE:
next();
access.add(TurbineModifier.VOLATILE);
break;
case STRICTFP:
next();
access.add(TurbineModifier.STRICTFP);
break;
case AT:
int pos = position;
next();
annos.add(annotation(pos));
break;
default:
return access;
}
}
}
private ImportDecl importDeclaration() {
boolean stat = maybe(Token.STATIC);
int pos = position;
ImmutableList.Builder<Ident> type = ImmutableList.builder();
type.add(eatIdent());
boolean wild = false;
OUTER:
while (maybe(Token.DOT)) {
switch (token) {
case IDENT:
type.add(eatIdent());
break;
case MULT:
eat(Token.MULT);
wild = true;
break OUTER;
default:
break;
}
}
eat(Token.SEMI);
return new ImportDecl(pos, type.build(), stat, wild);
}
private PkgDecl packageDeclaration(ImmutableList<Anno> annos) {
PkgDecl result = new PkgDecl(position, qualIdent(), annos);
eat(Token.SEMI);
return result;
}
private ImmutableList<Ident> qualIdent() {
ImmutableList.Builder<Ident> name = ImmutableList.builder();
name.add(eatIdent());
while (maybe(Token.DOT)) {
name.add(eatIdent());
}
return name.build();
}
private Anno annotation(int pos) {
ImmutableList<Ident> name = qualIdent();
ImmutableList.Builder<Expression> args = ImmutableList.builder();
if (token == Token.LPAREN) {
eat(LPAREN);
while (token != RPAREN) {
ConstExpressionParser cparser = new ConstExpressionParser(lexer, token, position);
Expression arg = cparser.expression();
if (arg == null) {
throw error(ErrorKind.INVALID_ANNOTATION_ARGUMENT);
}
args.add(arg);
token = cparser.token;
if (!maybe(COMMA)) {
break;
}
}
eat(Token.RPAREN);
}
return new Anno(pos, name, args.build());
}
private Ident ident() {
int position = lexer.position();
String value = lexer.stringValue();
return new Ident(position, value);
}
private Ident eatIdent() {
Ident ident = ident();
eat(Token.IDENT);
return ident;
}
private void eat(Token kind) {
if (token != kind) {
throw error(ErrorKind.EXPECTED_TOKEN, kind);
}
next();
}
@CanIgnoreReturnValue
private boolean maybe(Token kind) {
if (token == kind) {
next();
return true;
}
return false;
}
TurbineError error(Token token) {
switch (token) {
case IDENT:
return error(ErrorKind.UNEXPECTED_IDENTIFIER, lexer.stringValue());
case EOF:
return error(ErrorKind.UNEXPECTED_EOF);
default:
return error(ErrorKind.UNEXPECTED_TOKEN, token);
}
}
private TurbineError error(ErrorKind kind, Object... args) {
return TurbineError.format(
lexer.source(),
Math.min(lexer.position(), lexer.source().source().length() - 1),
kind,
args);
}
}