Initial check-in of skydoc rewrite.

This skydoc rewrite uses an actual skylark interpreter with a faked build API (implementing the actual build API that Bazel uses).

There's a lot left to do here, this is a barebones start.
For example, this does not yet handle:
  - load() statements
  - non-global build API elements (e.g. apple_common)
  - output of any rule information other than attribute names
  - markdown output format

RELNOTES: None.
PiperOrigin-RevId: 202187207
diff --git a/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
new file mode 100644
index 0000000..1e14cc1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skydoc/SkydocMain.java
@@ -0,0 +1,190 @@
+// Copyright 2018 The Bazel Authors. 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.devtools.build.skydoc;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.events.EventHandler;
+import com.google.devtools.build.lib.events.Location;
+import com.google.devtools.build.lib.skylarkbuildapi.TopLevelBootstrap;
+import com.google.devtools.build.lib.syntax.BuildFileAST;
+import com.google.devtools.build.lib.syntax.Environment;
+import com.google.devtools.build.lib.syntax.Environment.Extension;
+import com.google.devtools.build.lib.syntax.Environment.GlobalFrame;
+import com.google.devtools.build.lib.syntax.MethodLibrary;
+import com.google.devtools.build.lib.syntax.Mutability;
+import com.google.devtools.build.lib.syntax.ParserInputSource;
+import com.google.devtools.build.lib.syntax.Runtime;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeActionsInfoProvider;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeBuildApiGlobals;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeDefaultInfoProvider;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeOutputGroupInfo.FakeOutputGroupInfoProvider;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkAttrApi;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkCommandLineApi;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkNativeModuleApi;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeSkylarkRuleFunctionsApi;
+import com.google.devtools.build.skydoc.fakebuildapi.FakeStructApi.FakeStructProviderApi;
+import com.google.devtools.build.skydoc.rendering.RuleInfo;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Main entry point for the Skydoc binary.
+ *
+ * <p>Skydoc generates human-readable documentation for relevant details of skylark files by
+ * running a skylark interpreter with a fake implementation of the build API.</p>
+ *
+ * <p>Currently, Skydoc generates documentation for skylark rule definitions (discovered by
+ * invocations of the build API function {@code rule()}.</p>
+ *
+ * <p>Usage:</p>
+ * <pre>
+ *   skydoc {target_skylark_file} {output_file}
+ * </pre>
+ */
+public class SkydocMain {
+
+  // Pattern to match the assignment of a variable to a rule definition
+  // For example, 'my_rule = rule(' will match and have 'my_rule' available as group(1).
+  private static final Pattern ruleDefinitionLinePattern =
+      Pattern.compile("([^\\s]+) = rule\\(");
+
+  private final EventHandler eventHandler = new SystemOutEventHandler();
+
+  public static void main(String[] args) throws IOException, InterruptedException {
+    if (args.length != 2) {
+      throw new IllegalArgumentException("Expected two arguments. Usage:\n"
+          + "{skydoc_bin} {target_skylark_file} {output_file}");
+    }
+
+    String bzlPath = args[0];
+    String outputPath = args[1];
+
+    Path path = Paths.get(bzlPath);
+    byte[] content = Files.readAllBytes(path);
+
+    ParserInputSource parserInputSource =
+        ParserInputSource.create(content, PathFragment.create(path.toString()));
+
+    List<RuleInfo> ruleInfoList = new SkydocMain().eval(parserInputSource);
+
+    try (PrintWriter printWriter = new PrintWriter(outputPath, "UTF-8")) {
+      printRuleInfos(printWriter, ruleInfoList);
+    }
+  }
+
+  // TODO(cparsons): Improve output (markdown or HTML).
+  private static void printRuleInfos(
+      PrintWriter printWriter, List<RuleInfo> ruleInfos) throws IOException {
+    for (RuleInfo ruleInfo : ruleInfos) {
+      Location location = ruleInfo.getLocation();
+      Path filePath = Paths.get(location.getPath().getPathString());
+      List<String> lines = Files.readAllLines(filePath, UTF_8);
+      String definingString = lines.get(location.getStartLine() - 1);
+      // Rule definitions don't specify their own visible name directly. Instead, the name of
+      // a rule is dependent on the name of the variable assigend to the return value of rule().
+      // This attempts to find a line of the form 'foo = rule(' and thus label the rule as
+      // named 'foo'.
+      // TODO(cparsons): Inspect the global bindings of the environment instead of using string
+      // matching.
+      Matcher matcher = ruleDefinitionLinePattern.matcher(definingString);
+      if (matcher.matches()) {
+        printWriter.println(matcher.group(1));
+      } else {
+        printWriter.println("<unknown name>");
+      }
+      printWriter.println(ruleInfo.getDescription());
+    }
+  }
+
+  /**
+   * Evaluates/interprets the skylark file at the given input source using a fake build API and
+   * collects information about all rule definitions made in that file.
+   *
+   * @param parserInputSource the input source representing the input skylark file
+   * @return a list of {@link RuleInfo} objects describing the rule definitions
+   * @throws InterruptedException if evaluation is interrupted
+   */
+  // TODO(cparsons): Evaluate load statements recursively.
+  public List<RuleInfo> eval(ParserInputSource parserInputSource)
+      throws InterruptedException {
+    List<RuleInfo> ruleInfoList = new ArrayList<>();
+
+    BuildFileAST buildFileAST = BuildFileAST.parseSkylarkFile(
+        parserInputSource, eventHandler);
+
+    Environment env = createEnvironment(
+        eventHandler,
+        globalFrame(ruleInfoList),
+        /* imports= */ ImmutableMap.of());
+
+    if (!buildFileAST.exec(env, eventHandler)) {
+      throw new RuntimeException("Error loading file");
+    }
+
+    env.mutability().freeze();
+
+    return ruleInfoList;
+  }
+
+  /**
+   * Initialize and return a global frame containing the fake build API.
+   *
+   * @param ruleInfoList the list of {@link RuleInfo} objects, to which rule() invocation
+   *     information will be added
+   */
+  private static GlobalFrame globalFrame(List<RuleInfo> ruleInfoList) {
+    // TODO(cparsons): Complete the Fake Build API stubs. For example, implement provider(),
+    // and include the other bootstraps.
+    TopLevelBootstrap topLevelBootstrap =
+        new TopLevelBootstrap(new FakeBuildApiGlobals(),
+            new FakeSkylarkAttrApi(),
+            new FakeSkylarkCommandLineApi(),
+            new FakeSkylarkNativeModuleApi(),
+            new FakeSkylarkRuleFunctionsApi(ruleInfoList),
+            new FakeStructProviderApi(),
+            new FakeOutputGroupInfoProvider(),
+            new FakeActionsInfoProvider(),
+            new FakeDefaultInfoProvider());
+
+    ImmutableMap.Builder<String, Object> envBuilder = ImmutableMap.builder();
+
+    Runtime.addConstantsToBuilder(envBuilder);
+    MethodLibrary.addBindingsToBuilder(envBuilder);
+    topLevelBootstrap.addBindingsToBuilder(envBuilder);
+
+    return GlobalFrame.createForBuiltins(envBuilder.build());
+  }
+
+  private static Environment createEnvironment(EventHandler eventHandler, GlobalFrame globals,
+      Map<String, Extension> imports) {
+    return Environment.builder(Mutability.create("Skydoc"))
+        .useDefaultSemantics()
+        .setGlobals(globals)
+        .setImportedExtensions(imports)
+        .setEventHandler(eventHandler)
+        .build();
+  }
+}