Initial support for parsing module-infos.

MOE_MIGRATED_REVID=182253064
diff --git a/java/com/google/turbine/model/TurbineFlag.java b/java/com/google/turbine/model/TurbineFlag.java
index 18fc81e..37a11c3 100644
--- a/java/com/google/turbine/model/TurbineFlag.java
+++ b/java/com/google/turbine/model/TurbineFlag.java
@@ -30,6 +30,8 @@
   public static final int ACC_FINAL = 0x0010;
   public static final int ACC_SYNCHRONIZED = 0x0020;
   public static final int ACC_SUPER = 0x0020;
+  public static final int ACC_TRANSITIVE = 0x0020;
+  public static final int ACC_STATIC_PHASE = 0x0040;
   public static final int ACC_BRIDGE = 0x0040;
   public static final int ACC_VOLATILE = 0x0040;
   public static final int ACC_VARARGS = 0x0080;
diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java
index c528dc9..012e7e2 100644
--- a/java/com/google/turbine/parse/Parser.java
+++ b/java/com/google/turbine/parse/Parser.java
@@ -40,6 +40,13 @@
 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;
@@ -52,6 +59,7 @@
 import java.util.Deque;
 import java.util.EnumSet;
 import java.util.List;
+import javax.annotation.CheckReturnValue;
 import javax.annotation.Nullable;
 
 /**
@@ -85,6 +93,7 @@
     // and make it bug-compatible with javac:
     // http://mail.openjdk.java.net/pipermail/compiler-dev/2013-August/006968.html
     Optional<PkgDecl> pkg = Optional.absent();
+    Optional<ModDecl> mod = Optional.absent();
     EnumSet<TurbineModifier> access = EnumSet.noneOf(TurbineModifier.class);
     ImmutableList.Builder<ImportDecl> imports = ImmutableList.builder();
     ImmutableList.Builder<TyDecl> decls = ImmutableList.builder();
@@ -165,11 +174,30 @@
           break;
         case EOF:
           // TODO(cushon): check for dangling modifiers?
-          return new CompUnit(position, pkg, imports.build(), decls.build(), lexer.source());
+          return new CompUnit(position, pkg, mod, imports.build(), decls.build(), lexer.source());
         case SEMI:
           // TODO(cushon): check for dangling modifiers?
           next();
           continue;
+        case IDENT:
+          {
+            String ident = lexer.stringValue();
+            if (access.isEmpty() && (ident.equals("module") || ident.equals("open"))) {
+              boolean open = false;
+              if (ident.equals("open")) {
+                ident = eatIdent();
+                open = true;
+              }
+              if (!ident.equals("module")) {
+                throw error(token);
+              }
+              next();
+              mod = Optional.of(moduleDeclaration(open, annos.build()));
+              annos = ImmutableList.builder();
+              break;
+            }
+          }
+          // fall through
         default:
           throw error(token);
       }
@@ -259,6 +287,121 @@
         TurbineTyKind.ENUM);
   }
 
+  private ModDecl moduleDeclaration(boolean open, ImmutableList<Anno> annos) {
+    ImmutableList<String> moduleName = qualIdent();
+    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(position, annos, open, moduleName, directives.build());
+  }
+
+  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();
+        // TODO(cushon): note that this needs to lower to ACC_STATIC_PHASE, not ACC_STATIC
+        access.add(TurbineModifier.STATIC);
+        break;
+      }
+      break;
+    }
+    ImmutableList<String> moduleName = qualIdent();
+    eat(Token.SEMI);
+    return new ModRequires(pos, ImmutableSet.copyOf(access), moduleName);
+  }
+
+  private ModExports moduleExports() {
+    int pos = position;
+    ImmutableList<String> packageName = qualIdent();
+    ImmutableList.Builder<ImmutableList<String>> moduleNames = ImmutableList.builder();
+    if (lexer.stringValue().equals("to")) {
+      next();
+      do {
+        ImmutableList<String> moduleName = qualIdent();
+        moduleNames.add(moduleName);
+      } while (maybe(Token.COMMA));
+    }
+    eat(Token.SEMI);
+    return new ModExports(pos, packageName, moduleNames.build());
+  }
+
+  private ModOpens moduleOpens() {
+    int pos = position;
+    ImmutableList<String> packageName = qualIdent();
+    ImmutableList.Builder<ImmutableList<String>> moduleNames = ImmutableList.builder();
+    if (lexer.stringValue().equals("to")) {
+      next();
+      do {
+        ImmutableList<String> moduleName = qualIdent();
+        moduleNames.add(moduleName);
+      } while (maybe(Token.COMMA));
+    }
+    eat(Token.SEMI);
+    return new ModOpens(pos, packageName, moduleNames.build());
+  }
+
+  private ModUses moduleUses() {
+    int pos = position;
+    ImmutableList<String> uses = qualIdent();
+    eat(Token.SEMI);
+    return new ModUses(pos, uses);
+  }
+
+  private ModProvides moduleProvides() {
+    int pos = position;
+    ImmutableList<String> typeName = qualIdent();
+    if (!eatIdent().equals("with")) {
+      throw error(token);
+    }
+    ImmutableList.Builder<ImmutableList<String>> implNames = ImmutableList.builder();
+    do {
+      ImmutableList<String> 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,
@@ -1181,6 +1324,7 @@
     return false;
   }
 
+  @CheckReturnValue
   TurbineError error(Token token) {
     switch (token) {
       case IDENT:
@@ -1192,6 +1336,7 @@
     }
   }
 
+  @CheckReturnValue
   private TurbineError error(ErrorKind kind, Object... args) {
     return TurbineError.format(
         lexer.source(),
diff --git a/java/com/google/turbine/tree/Pretty.java b/java/com/google/turbine/tree/Pretty.java
index 820126d..be677f3 100644
--- a/java/com/google/turbine/tree/Pretty.java
+++ b/java/com/google/turbine/tree/Pretty.java
@@ -22,6 +22,13 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.turbine.tree.Tree.Anno;
 import com.google.turbine.tree.Tree.ClassLiteral;
+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 java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -237,6 +244,10 @@
     for (Tree.ImportDecl i : compUnit.imports()) {
       i.accept(this, null);
     }
+    if (compUnit.mod().isPresent()) {
+      printLine();
+      compUnit.mod().get().accept(this, null);
+    }
     for (Tree.TyDecl decl : compUnit.decls()) {
       printLine();
       decl.accept(this, null);
@@ -472,6 +483,7 @@
         case NATIVE:
         case TRANSIENT:
         case DEFAULT:
+        case TRANSITIVE:
           append(mod.toString()).append(' ');
           break;
         case ACC_SUPER:
@@ -515,4 +527,109 @@
     append("package ").append(Joiner.on('.').join(pkgDecl.name())).append(';');
     return null;
   }
+
+  @Override
+  public Void visitModDecl(ModDecl modDecl, Void input) {
+    for (Tree.Anno anno : modDecl.annos()) {
+      anno.accept(this, null);
+      printLine();
+    }
+    if (modDecl.open()) {
+      append("open ");
+    }
+    append("module ").append(Joiner.on('.').join(modDecl.moduleName())).append(" {");
+    indent++;
+    append('\n');
+    for (ModDirective directive : modDecl.directives()) {
+      directive.accept(this, null);
+    }
+    indent--;
+    append("}\n");
+    return null;
+  }
+
+  @Override
+  public Void visitModRequires(ModRequires modRequires, Void input) {
+    append("requires ");
+    printModifiers(modRequires.mods());
+    append(Joiner.on('.').join(modRequires.moduleName()));
+    append(";");
+    append('\n');
+    return null;
+  }
+
+  @Override
+  public Void visitModExports(ModExports modExports, Void input) {
+    append("exports ");
+    append(Joiner.on('.').join(modExports.packageName()));
+    if (!modExports.moduleNames().isEmpty()) {
+      append(" to").append('\n');
+      indent += 2;
+      boolean first = true;
+      for (ImmutableList<String> moduleName : modExports.moduleNames()) {
+        if (!first) {
+          append(',').append('\n');
+        }
+        append(Joiner.on('.').join(moduleName));
+        first = false;
+      }
+      indent -= 2;
+    }
+    append(";");
+    append('\n');
+    return null;
+  }
+
+  @Override
+  public Void visitModOpens(ModOpens modOpens, Void input) {
+    append("opens ");
+    append(Joiner.on('.').join(modOpens.packageName()));
+    if (!modOpens.moduleNames().isEmpty()) {
+      append(" to").append('\n');
+      indent += 2;
+      boolean first = true;
+      for (ImmutableList<String> moduleName : modOpens.moduleNames()) {
+        if (!first) {
+          append(',').append('\n');
+        }
+        append(Joiner.on('.').join(moduleName));
+        first = false;
+      }
+      indent -= 2;
+    }
+    append(";");
+    append('\n');
+    return null;
+  }
+
+  @Override
+  public Void visitModUses(ModUses modUses, Void input) {
+    append("uses ");
+    append(Joiner.on('.').join(modUses.typeName()));
+    append(";");
+    append('\n');
+    return null;
+  }
+
+  @Override
+  public Void visitModProvides(ModProvides modProvides, Void input) {
+    append("provides ");
+    append(Joiner.on('.').join(modProvides.typeName()));
+    if (!modProvides.implNames().isEmpty()) {
+      append(" with").append('\n');
+      indent += 2;
+      boolean first = true;
+      for (ImmutableList<String> implName : modProvides.implNames()) {
+        if (!first) {
+          append(',').append('\n');
+        }
+        append(Joiner.on('.').join(implName));
+        first = false;
+      }
+      indent -= 2;
+    }
+    append(";");
+    append('\n');
+    return null;
+  }
 }
diff --git a/java/com/google/turbine/tree/Tree.java b/java/com/google/turbine/tree/Tree.java
index 6f46df5..16e9341 100644
--- a/java/com/google/turbine/tree/Tree.java
+++ b/java/com/google/turbine/tree/Tree.java
@@ -71,7 +71,13 @@
     ANNO_EXPR,
     TY_DECL,
     TY_PARAM,
-    PKG_DECL
+    PKG_DECL,
+    MOD_DECL,
+    MOD_REQUIRES,
+    MOD_EXPORTS,
+    MOD_OPENS,
+    MOD_USES,
+    MOD_PROVIDES
   }
 
   /** A type use. */
@@ -524,6 +530,7 @@
   /** A JLS 7.3 compilation unit. */
   public static class CompUnit extends Tree {
     private final Optional<PkgDecl> pkg;
+    private final Optional<ModDecl> mod;
     private final ImmutableList<ImportDecl> imports;
     private final ImmutableList<TyDecl> decls;
     private final SourceFile source;
@@ -531,11 +538,13 @@
     public CompUnit(
         int position,
         Optional<PkgDecl> pkg,
+        Optional<ModDecl> mod,
         ImmutableList<ImportDecl> imports,
         ImmutableList<TyDecl> decls,
         SourceFile source) {
       super(position);
       this.pkg = pkg;
+      this.mod = mod;
       this.imports = imports;
       this.decls = decls;
       this.source = source;
@@ -555,6 +564,10 @@
       return pkg;
     }
 
+    public Optional<ModDecl> mod() {
+      return mod;
+    }
+
     public ImmutableList<ImportDecl> imports() {
       return imports;
     }
@@ -936,6 +949,257 @@
     }
   }
 
+  /** A JLS 7.7 module declaration. */
+  public static class ModDecl extends Tree {
+
+    private final ImmutableList<Anno> annos;
+    private final boolean open;
+    private final ImmutableList<String> moduleName;
+    private final ImmutableList<ModDirective> directives;
+
+    public ModDecl(
+        int position,
+        ImmutableList<Anno> annos,
+        boolean open,
+        ImmutableList<String> moduleName,
+        ImmutableList<ModDirective> directives) {
+      super(position);
+      this.annos = annos;
+      this.open = open;
+      this.moduleName = moduleName;
+      this.directives = directives;
+    }
+
+    public boolean open() {
+      return open;
+    }
+
+    public ImmutableList<Anno> annos() {
+      return annos;
+    }
+
+    public ImmutableList<String> moduleName() {
+      return moduleName;
+    }
+
+    public ImmutableList<ModDirective> directives() {
+      return directives;
+    }
+
+    @Override
+    public Kind kind() {
+      return Kind.MOD_DECL;
+    }
+
+    @Override
+    public <I, O> O accept(Visitor<I, O> visitor, I input) {
+      return visitor.visitModDecl(this, input);
+    }
+  }
+
+  /** A kind of module directive. */
+  public abstract static class ModDirective extends Tree {
+
+    /** A module directive kind. */
+    public enum DirectiveKind {
+      REQUIRES,
+      EXPORTS,
+      OPENS,
+      USES,
+      PROVIDES
+    }
+
+    public abstract DirectiveKind directiveKind();
+
+    protected ModDirective(int position) {
+      super(position);
+    }
+  }
+
+  /** A JLS 7.7.1 module requires directive. */
+  public static class ModRequires extends ModDirective {
+
+    private final ImmutableSet<TurbineModifier> mods;
+    private final ImmutableList<String> moduleName;
+
+    @Override
+    public Kind kind() {
+      return Kind.MOD_REQUIRES;
+    }
+
+    @Override
+    public <I, O> O accept(Visitor<I, O> visitor, I input) {
+      return visitor.visitModRequires(this, input);
+    }
+
+    public ModRequires(
+        int position, ImmutableSet<TurbineModifier> mods, ImmutableList<String> moduleName) {
+      super(position);
+      this.mods = mods;
+      this.moduleName = moduleName;
+    }
+
+    public ImmutableSet<TurbineModifier> mods() {
+      return mods;
+    }
+
+    public ImmutableList<String> moduleName() {
+      return moduleName;
+    }
+
+    @Override
+    public DirectiveKind directiveKind() {
+      return DirectiveKind.REQUIRES;
+    }
+  }
+
+  /** A JLS 7.7.2 module exports directive. */
+  public static class ModExports extends ModDirective {
+
+    private final ImmutableList<String> packageName;
+    private final ImmutableList<ImmutableList<String>> moduleNames;
+
+    @Override
+    public Kind kind() {
+      return Kind.MOD_EXPORTS;
+    }
+
+    @Override
+    public <I, O> O accept(Visitor<I, O> visitor, I input) {
+      return visitor.visitModExports(this, input);
+    }
+
+    public ModExports(
+        int position,
+        ImmutableList<String> packageName,
+        ImmutableList<ImmutableList<String>> moduleNames) {
+      super(position);
+      this.packageName = packageName;
+      this.moduleNames = moduleNames;
+    }
+
+    public ImmutableList<String> packageName() {
+      return packageName;
+    }
+
+    public ImmutableList<ImmutableList<String>> moduleNames() {
+      return moduleNames;
+    }
+
+    @Override
+    public DirectiveKind directiveKind() {
+      return DirectiveKind.EXPORTS;
+    }
+  }
+
+  /** A JLS 7.7.2 module opens directive. */
+  public static class ModOpens extends ModDirective {
+
+    private final ImmutableList<String> packageName;
+    private final ImmutableList<ImmutableList<String>> moduleNames;
+
+    public ModOpens(
+        int position,
+        ImmutableList<String> packageName,
+        ImmutableList<ImmutableList<String>> moduleNames) {
+      super(position);
+      this.packageName = packageName;
+      this.moduleNames = moduleNames;
+    }
+
+    public ImmutableList<String> packageName() {
+      return packageName;
+    }
+
+    public ImmutableList<ImmutableList<String>> moduleNames() {
+      return moduleNames;
+    }
+
+    @Override
+    public Kind kind() {
+      return Kind.MOD_OPENS;
+    }
+
+    @Override
+    public <I, O> O accept(Visitor<I, O> visitor, I input) {
+      return visitor.visitModOpens(this, input);
+    }
+
+    @Override
+    public DirectiveKind directiveKind() {
+      return DirectiveKind.OPENS;
+    }
+  }
+
+  /** A JLS 7.7.3 module uses directive. */
+  public static class ModUses extends ModDirective {
+
+    private final ImmutableList<String> typeName;
+
+    public ModUses(int position, ImmutableList<String> typeName) {
+      super(position);
+      this.typeName = typeName;
+    }
+
+    public ImmutableList<String> typeName() {
+      return typeName;
+    }
+
+    @Override
+    public Kind kind() {
+      return Kind.MOD_USES;
+    }
+
+    @Override
+    public <I, O> O accept(Visitor<I, O> visitor, I input) {
+      return visitor.visitModUses(this, input);
+    }
+
+    @Override
+    public DirectiveKind directiveKind() {
+      return DirectiveKind.USES;
+    }
+  }
+
+  /** A JLS 7.7.4 module uses directive. */
+  public static class ModProvides extends ModDirective {
+
+    private final ImmutableList<String> typeName;
+    private final ImmutableList<ImmutableList<String>> implNames;
+
+    public ModProvides(
+        int position,
+        ImmutableList<String> typeName,
+        ImmutableList<ImmutableList<String>> implNames) {
+      super(position);
+      this.typeName = typeName;
+      this.implNames = implNames;
+    }
+
+    public ImmutableList<String> typeName() {
+      return typeName;
+    }
+
+    public ImmutableList<ImmutableList<String>> implNames() {
+      return implNames;
+    }
+
+    @Override
+    public Kind kind() {
+      return Kind.MOD_PROVIDES;
+    }
+
+    @Override
+    public <I, O> O accept(Visitor<I, O> visitor, I input) {
+      return visitor.visitModProvides(this, input);
+    }
+
+    @Override
+    public DirectiveKind directiveKind() {
+      return DirectiveKind.PROVIDES;
+    }
+  }
+
   /** A visitor for {@link Tree}s. */
   public interface Visitor<I, O> {
     O visitWildTy(WildTy visitor, I input);
@@ -981,5 +1245,17 @@
     O visitTyParam(TyParam tyParam, I input);
 
     O visitPkgDecl(PkgDecl pkgDecl, I input);
+
+    O visitModDecl(ModDecl modDecl, I input);
+
+    O visitModRequires(ModRequires modRequires, I input);
+
+    O visitModExports(ModExports modExports, I input);
+
+    O visitModOpens(ModOpens modOpens, I input);
+
+    O visitModUses(ModUses modUses, I input);
+
+    O visitModProvides(ModProvides modProvides, I input);
   }
 }
diff --git a/java/com/google/turbine/tree/TurbineModifier.java b/java/com/google/turbine/tree/TurbineModifier.java
index 3d0e60d..35dc11c 100644
--- a/java/com/google/turbine/tree/TurbineModifier.java
+++ b/java/com/google/turbine/tree/TurbineModifier.java
@@ -44,7 +44,8 @@
   ACC_ANNOTATION(TurbineFlag.ACC_ANNOTATION),
   ACC_SYNTHETIC(TurbineFlag.ACC_SYNTHETIC),
   ACC_BRIDGE(TurbineFlag.ACC_BRIDGE),
-  DEFAULT(TurbineFlag.ACC_DEFAULT);
+  DEFAULT(TurbineFlag.ACC_DEFAULT),
+  TRANSITIVE(TurbineFlag.ACC_TRANSITIVE);
 
   private final int flag;
 
diff --git a/javatests/com/google/turbine/parse/ParserIntegrationTest.java b/javatests/com/google/turbine/parse/ParserIntegrationTest.java
index d856cb3..2503553 100644
--- a/javatests/com/google/turbine/parse/ParserIntegrationTest.java
+++ b/javatests/com/google/turbine/parse/ParserIntegrationTest.java
@@ -21,6 +21,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Function;
+import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
 import com.google.common.io.CharStreams;
 import com.google.turbine.tree.Tree;
@@ -28,6 +29,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.Arrays;
+import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -72,6 +74,7 @@
       "packinfo1.input",
       "weirdstring.input",
       "type_annotations.input",
+      "module-info.input",
     };
     return Iterables.transform(
         Arrays.asList(tests),
@@ -97,9 +100,9 @@
     try (InputStreamReader in = new InputStreamReader(stream, UTF_8)) {
       result = CharStreams.toString(in);
     }
-    String[] pieces = result.split("===+");
-    String input = pieces[0].trim();
-    String expected = pieces.length > 1 ? pieces[1].trim() : input;
+    List<String> pieces = Splitter.onPattern("===+").splitToList(result);
+    String input = pieces.get(0).trim();
+    String expected = pieces.size() > 1 ? pieces.get(1).trim() : input;
     Tree.CompUnit unit = Parser.parse(input);
     assertThat(unit.toString().trim()).isEqualTo(expected);
   }
diff --git a/javatests/com/google/turbine/parse/testdata/module-info.input b/javatests/com/google/turbine/parse/testdata/module-info.input
new file mode 100644
index 0000000..892a3b1
--- /dev/null
+++ b/javatests/com/google/turbine/parse/testdata/module-info.input
@@ -0,0 +1,29 @@
+import a.A;
+import a.B;
+import com.google.Foo;
+import com.google.Baz;
+
+@A
+@B
+module com.google.m {
+  requires java.compiler;
+  requires transitive jdk.compiler;
+  requires static java.base;
+  exports com.google.p1;
+  exports com.google.p2 to
+      java.base;
+  exports com.google.p3 to
+      java.base,
+      java.compiler;
+  opens com.google.p1;
+  opens com.google.p2 to
+      java.base;
+  opens com.google.p3 to
+      java.base,
+      java.compiler;
+  uses Foo;
+  uses com.google.Bar;
+  provides com.google.Baz with
+      Foo,
+      com.google.Bar;
+}