Просмотр исходного кода

Add a rough outline of a command line driver. (#548)

This is actually code that I wrote a long time ago but didn't get added
to the repository, trying to tidy that up. This is the last code.
Nothing much interesting here, just a skeleton of a CLI. But seemed
better to add it than to not.
Chandler Carruth 4 лет назад
Родитель
Сommit
8afc1f8316
95 измененных файлов с 672 добавлено и 0 удалено
  1. 63 0
      driver/BUILD
  2. 24 0
      driver/carbon_test.carbon
  3. 141 0
      driver/driver.cpp
  4. 70 0
      driver/driver.h
  5. 82 0
      driver/driver_fuzzer.cpp
  6. 21 0
      driver/driver_main.cpp
  7. 245 0
      driver/driver_test.cpp
  8. 19 0
      driver/flags.def
  9. BIN
      driver/fuzzer_corpus/019dd4030b151d2c67da557bf3d56d96dc7839c1
  10. BIN
      driver/fuzzer_corpus/02c83534aab9233198863881aa7d82dde0a5b980
  11. BIN
      driver/fuzzer_corpus/059a104f98f5658171c48a4d6b0d39036f953264
  12. BIN
      driver/fuzzer_corpus/0ab2969c765b7dfd5a132f93c87d60f700089d26
  13. BIN
      driver/fuzzer_corpus/11780e1967cb34483305ee0ce43df22b498db8de
  14. BIN
      driver/fuzzer_corpus/140803cdfca738e7d23521f13ecf6d87d0bd8980
  15. BIN
      driver/fuzzer_corpus/1b65c7f425af22130c7c3aa117961873a194a8ef
  16. BIN
      driver/fuzzer_corpus/2251142ebdcd99890424c932de502d094925ad98
  17. BIN
      driver/fuzzer_corpus/256dbb574b157de6b00c720c6e123de5d3129e56
  18. BIN
      driver/fuzzer_corpus/26305a3c41da25872752f99d86ba0cff5c96c15a
  19. BIN
      driver/fuzzer_corpus/285ad940d4ba382b1ca2698edae0f48a132d3f4c
  20. BIN
      driver/fuzzer_corpus/2d8c6aebe55a5df4c61804509d3ff1c14978c578
  21. BIN
      driver/fuzzer_corpus/2f99d3b7e96de13b21d1b33aa77d5d48fc6e6f88
  22. BIN
      driver/fuzzer_corpus/2fd2a7b121a56fdc1e623de01dc1678414adc5dc
  23. BIN
      driver/fuzzer_corpus/300ba8e152fe095fcd1f88d18bc64d5b100059e6
  24. BIN
      driver/fuzzer_corpus/34c0284640cf11c78b5802ecaf21802ebc83df04
  25. BIN
      driver/fuzzer_corpus/3519a9e1095165b91c5d4ba9824d32227da30bd6
  26. BIN
      driver/fuzzer_corpus/39294db001ba643d5353c8f418f754c6352c8485
  27. BIN
      driver/fuzzer_corpus/3c4c33f72a9ff968e60adf7a2db5206d6375b7c5
  28. BIN
      driver/fuzzer_corpus/3c585604e87f855973731fea83e21fab9392d2fc
  29. BIN
      driver/fuzzer_corpus/3da89ee273be13437e7ecf760f3fbd4dc0e8d1fe
  30. 3 0
      driver/fuzzer_corpus/3dbc1b67cab9a71bac41d7ee17fa31a8b32a9904
  31. BIN
      driver/fuzzer_corpus/4148ee13ed2c74d0f5ce52c46ebd0983c26beb64
  32. BIN
      driver/fuzzer_corpus/45f063671e63a4a29f2b05bdd3b6731568119e38
  33. BIN
      driver/fuzzer_corpus/4c6f81cf8a9546cd87d1fce006bbe64abd47697b
  34. BIN
      driver/fuzzer_corpus/4e0f684219dcb4518aaf4166bd2b51c957ef36de
  35. BIN
      driver/fuzzer_corpus/508abb20ddc995fe9e72b69216fd46c24c966358
  36. BIN
      driver/fuzzer_corpus/58607edfe3d6ae988025bc9062e244d4c47f54dc
  37. BIN
      driver/fuzzer_corpus/58aaf472c4ed651739b9b6debf73437e4eaafb0e
  38. BIN
      driver/fuzzer_corpus/596cf9d9a653583c4753d4f51b95e679b63f7c71
  39. BIN
      driver/fuzzer_corpus/5dab5e799f3875176eeaa35987db34271c1067a0
  40. BIN
      driver/fuzzer_corpus/64e1cd6e55c120c9f2fb329773f345ee6406b74a
  41. BIN
      driver/fuzzer_corpus/67cba3df1acabd24c9b66e3c02b819f25f3fcf57
  42. BIN
      driver/fuzzer_corpus/6854bbf33c0126b61050dd5fc0ef0126a9701ff0
  43. BIN
      driver/fuzzer_corpus/6b03ca7a6835a17b1077f1b98f43c63efd3cac13
  44. BIN
      driver/fuzzer_corpus/6ba597db92a7d33adb74499c15e5034ee57cd41b
  45. BIN
      driver/fuzzer_corpus/6cd45830db903347e26c3e114ba494052a4a42bd
  46. BIN
      driver/fuzzer_corpus/6f3f7dcc0a69d64ed2d99bcee0c0fbb81f970fc6
  47. BIN
      driver/fuzzer_corpus/74a21fca37339bc7223b8963473dff771ae964f5
  48. BIN
      driver/fuzzer_corpus/78d5bd054d5f82b116ac00b834314d4e24f6c21b
  49. BIN
      driver/fuzzer_corpus/79119613c827b0d5bea100613dcd8aa8765c31f6
  50. BIN
      driver/fuzzer_corpus/798bd7bd8e4d8d1835ffe6f02fbeb6319f4f106e
  51. BIN
      driver/fuzzer_corpus/7bc4f9278deaa7046a5f512c89eac67ebf3ba0d3
  52. BIN
      driver/fuzzer_corpus/86179d08482c27dab51fe8a8ea5d8324ef2461e9
  53. 3 0
      driver/fuzzer_corpus/8b0653417087d3d7fd049166a72cd7bcbc5f6dd1
  54. BIN
      driver/fuzzer_corpus/8cabec887fe80b7d9f8b28e989db65a6d72dd22b
  55. BIN
      driver/fuzzer_corpus/8eda19d8bfbc4dbd6070bf626cd2f33160ff88a0
  56. BIN
      driver/fuzzer_corpus/9069ca78e7450a285173431b3e52c5c25299e473
  57. BIN
      driver/fuzzer_corpus/922b5a97efaa1a3ecba58234ad8d01f41d36d313
  58. BIN
      driver/fuzzer_corpus/9d47be29ac84966acf09290f8a7df5ba0ac4ea4e
  59. BIN
      driver/fuzzer_corpus/a2ae2a7db83b33dc95396607258f553114c9183c
  60. BIN
      driver/fuzzer_corpus/a454ca483b4a66b83826d061be2859dd79ff0d6c
  61. BIN
      driver/fuzzer_corpus/a5bbebbdf11c537a22a3a9dab4b83498ceb441ca
  62. BIN
      driver/fuzzer_corpus/a69f09257d9cd8f5edd9a87b728ab3d75d4352c4
  63. BIN
      driver/fuzzer_corpus/a8502f9cfc7efd4ddb3cb771ae00364f60bc279f
  64. 1 0
      driver/fuzzer_corpus/adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
  65. BIN
      driver/fuzzer_corpus/aecfd3909eadc5707dd46249f6239cad4a2d8618
  66. BIN
      driver/fuzzer_corpus/af0fb9fda0e123b97ba7be099545a02b8c21a058
  67. BIN
      driver/fuzzer_corpus/af1ec8ae5cda07dc979bd68e45d67ffe0d7ffed6
  68. BIN
      driver/fuzzer_corpus/b36251f56081e4735b65b1c9d3d765f25ce67a66
  69. BIN
      driver/fuzzer_corpus/b62c8e9630f2bb2106ce5717ecf7f10c1b89b958
  70. BIN
      driver/fuzzer_corpus/b74fd0148ec8333ad7ce831dd7caa6bc4eff73a9
  71. BIN
      driver/fuzzer_corpus/b82f012f0ede708c493207a1eeee577e3c0f884c
  72. BIN
      driver/fuzzer_corpus/bb709bdd1d83c039d5c2f02313009030a4f73e0f
  73. BIN
      driver/fuzzer_corpus/bc75eaba7c2f73ce991969295463f6af70bfbf1a
  74. BIN
      driver/fuzzer_corpus/be0ef7b40ed46b91af0482ce0eb7ad38042b5659
  75. BIN
      driver/fuzzer_corpus/beacd06c224890fcf6d9b09b771e1248e241ab4b
  76. BIN
      driver/fuzzer_corpus/c349a8a58eb9c57bf1c97e5af483db5144482772
  77. BIN
      driver/fuzzer_corpus/c673fa568bbd98569d347b44306c46b50cc2e53f
  78. BIN
      driver/fuzzer_corpus/c949526372921c8c5a75ed9e1b198f44fb7a96a1
  79. BIN
      driver/fuzzer_corpus/cf294d20a4419644a7716bc1e1783a770c5e2e33
  80. BIN
      driver/fuzzer_corpus/cfa74701753e8c5eda1d7364b63b26b667c71bb2
  81. BIN
      driver/fuzzer_corpus/d25a8e16325922f85262deec08e77c04acf69f8e
  82. BIN
      driver/fuzzer_corpus/d5ebc4110136110c161d48f2d8e53cd1b4edfc0d
  83. BIN
      driver/fuzzer_corpus/d97e2e42e26d865d34f68bc92cf8bfe6a40f2eb4
  84. BIN
      driver/fuzzer_corpus/e3d8d13265fdbea01751381344b1a7466fb0cdfb
  85. BIN
      driver/fuzzer_corpus/e4cd6da031338746e34eb86e8a931d3b589e3d50
  86. BIN
      driver/fuzzer_corpus/e8e9adab43ade785323869812e73201de7abf7ad
  87. BIN
      driver/fuzzer_corpus/ea21645db01d3810168f14c8dfa601d54d7a52f5
  88. BIN
      driver/fuzzer_corpus/ed301991137b18831e72c6a6045fac64e5c010c9
  89. BIN
      driver/fuzzer_corpus/efa312d99ad0a086577dfb243164f522aa921b86
  90. BIN
      driver/fuzzer_corpus/f488a972b82d572833fccb4600a74d8d4a33ff84
  91. BIN
      driver/fuzzer_corpus/f4d6728f4d7cb31739d3ed633a564e4f0015d7cf
  92. BIN
      driver/fuzzer_corpus/fb80d614f998904a2fcee5f4ac25ce3e71787364
  93. BIN
      driver/fuzzer_corpus/fbd4c32be3552f308d7b77155fe35dab861cf45c
  94. BIN
      driver/fuzzer_corpus/fd354923ab6a356b717147ef68e51581e81ec8c8
  95. BIN
      driver/fuzzer_corpus/fe0ff78dd1420db832b271cc868019e9945f5f79

+ 63 - 0
driver/BUILD

@@ -0,0 +1,63 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
+load("//bazel/fuzzing:rules.bzl", "cc_fuzz_test")
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+    name = "driver",
+    srcs = ["driver.cpp"],
+    hdrs = ["driver.h"],
+    textual_hdrs = ["flags.def"],
+    deps = [
+        "//diagnostics:diagnostic_emitter",
+        "//lexer:tokenized_buffer",
+        "//source:source_buffer",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+cc_test(
+    name = "driver_test",
+    srcs = ["driver_test.cpp"],
+    deps = [
+        ":driver",
+        "//lexer:tokenized_buffer_test_helpers",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//llvm:gmock",
+        "@llvm-project//llvm:gtest",
+        "@llvm-project//llvm:gtest_main",
+    ],
+)
+
+cc_fuzz_test(
+    name = "driver_fuzzer",
+    srcs = ["driver_fuzzer.cpp"],
+    corpus = glob(["fuzzer_corpus/*"]),
+    deps = [
+        ":driver",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+cc_binary(
+    name = "carbon",
+    srcs = ["driver_main.cpp"],
+    deps = [
+        ":driver",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+# FIXME: No support for LLVM's lit-style command line & FileCheck tests.
+#
+#lit_test(
+#    name = "carbon_test.carbon",
+#    data = [
+#        ":carbon",
+#        "@llvm-project//llvm:FileCheck",
+#    ],
+#)

+ 24 - 0
driver/carbon_test.carbon

@@ -0,0 +1,24 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// FIXME: We need to figure out how to integrate this style of integration
+// testing with Bazel.
+//
+// RUN: driver/carbon dump-tokens %s | FileCheck %s --check-prefix=TOKENS
+
+fn run(String program) {
+  return True;
+}
+
+// TOKENS: token: { index:  0, kind:       'FnKeyword', line: 4, column:  1, indent: 1, spelling: 'fn' }
+// TOKENS: token: { index:  1, kind:      'Identifier', line: 4, column:  4, indent: 1, spelling: 'run', identifier: 0 }
+// TOKENS: token: { index:  2, kind:       'OpenParen', line: 4, column:  7, indent: 1, spelling: '(', closing_token: 5 }
+// TOKENS: token: { index:  3, kind:      'Identifier', line: 4, column:  8, indent: 1, spelling: 'String', identifier: 1 }
+// TOKENS: token: { index:  4, kind:      'Identifier', line: 4, column: 15, indent: 1, spelling: 'program', identifier: 2 }
+// TOKENS: token: { index:  5, kind:      'CloseParen', line: 4, column: 22, indent: 1, spelling: ')', opening_token: 2 }
+// TOKENS: token: { index:  6, kind:  'OpenCurlyBrace', line: 4, column: 24, indent: 1, spelling: '{', closing_token: 10 }
+// TOKENS: token: { index:  7, kind:   'ReturnKeyword', line: 5, column:  3, indent: 3, spelling: 'return' }
+// TOKENS: token: { index:  8, kind:      'Identifier', line: 5, column: 10, indent: 3, spelling: 'True', identifier: 3 }
+// TOKENS: token: { index:  9, kind:            'Semi', line: 5, column: 14, indent: 3, spelling: ';' }
+// TOKENS: token: { index: 10, kind: 'CloseCurlyBrace', line: 6, column:  1, indent: 1, spelling: '}', opening_token: 6 }

+ 141 - 0
driver/driver.cpp

@@ -0,0 +1,141 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "driver/driver.h"
+
+#include "diagnostics/diagnostic_emitter.h"
+#include "lexer/tokenized_buffer.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/Format.h"
+#include "source/source_buffer.h"
+
+namespace Carbon {
+
+namespace {
+
+enum class Subcommand {
+#define CARBON_SUBCOMMAND(Name, ...) Name,
+#include "driver/flags.def"
+  Unknown,
+};
+
+auto GetSubcommand(llvm::StringRef name) -> Subcommand {
+  return llvm::StringSwitch<Subcommand>(name)
+#define CARBON_SUBCOMMAND(Name, Spelling, ...) .Case(Spelling, Subcommand::Name)
+#include "driver/flags.def"
+      .Default(Subcommand::Unknown);
+}
+
+}  // namespace
+
+auto Driver::RunFullCommand(llvm::ArrayRef<llvm::StringRef> args) -> bool {
+  if (args.empty()) {
+    error_stream << "ERROR: No subcommand specified.\n";
+    return false;
+  }
+
+  llvm::StringRef subcommand_text = args[0];
+  llvm::SmallVector<llvm::StringRef, 16> subcommand_args(
+      std::next(args.begin()), args.end());
+  switch (GetSubcommand(subcommand_text)) {
+    case Subcommand::Unknown:
+      error_stream << "ERROR: Unknown subcommand '" << subcommand_text
+                   << "'.\n";
+      return false;
+
+#define CARBON_SUBCOMMAND(Name, ...) \
+  case Subcommand::Name:             \
+    return Run##Name##Subcommand(subcommand_args);
+#include "driver/flags.def"
+  }
+  llvm_unreachable("All subcommands handled!");
+}
+
+auto Driver::RunHelpSubcommand(llvm::ArrayRef<llvm::StringRef> args) -> bool {
+  // FIXME: We should support getting detailed help on a subcommand by looking
+  // for it as a positional parameter here.
+  if (!args.empty()) {
+    ReportExtraArgs("help", args);
+    return false;
+  }
+
+  output_stream << "List of subcommands:\n\n";
+
+  constexpr llvm::StringLiteral SubcommandsAndHelp[][2] = {
+#define CARBON_SUBCOMMAND(Name, Spelling, HelpText) {Spelling, HelpText},
+#include "driver/flags.def"
+  };
+
+  int max_subcommand_width = 0;
+  for (auto subcommand_and_help : SubcommandsAndHelp) {
+    max_subcommand_width = std::max(
+        max_subcommand_width, static_cast<int>(subcommand_and_help[0].size()));
+  }
+
+  for (auto subcommand_and_help : SubcommandsAndHelp) {
+    llvm::StringRef subcommand_text = subcommand_and_help[0];
+    // FIXME: We should wrap this to the number of columns left after the
+    // subcommand on the terminal, and using a hanging indent.
+    llvm::StringRef help_text = subcommand_and_help[1];
+    output_stream << "  "
+                  << llvm::left_justify(subcommand_text, max_subcommand_width)
+                  << " - " << help_text << "\n";
+  }
+
+  output_stream << "\n";
+  return true;
+}
+
+auto Driver::RunDumpTokensSubcommand(llvm::ArrayRef<llvm::StringRef> args)
+    -> bool {
+  if (args.empty()) {
+    error_stream << "ERROR: No input file specified.\n";
+    return false;
+  }
+
+  llvm::StringRef input_file_name = args.front();
+  args = args.drop_front();
+  if (!args.empty()) {
+    ReportExtraArgs("dump-tokens", args);
+    return false;
+  }
+
+  auto source = SourceBuffer::CreateFromFile(input_file_name);
+  if (!source) {
+    error_stream << "ERROR: Unable to open input source file: ";
+    llvm::handleAllErrors(source.takeError(),
+                          [&](const llvm::ErrorInfoBase& ei) {
+                            ei.log(error_stream);
+                            error_stream << "\n";
+                          });
+    return false;
+  }
+  auto tokenized_source =
+      TokenizedBuffer::Lex(*source, ConsoleDiagnosticConsumer());
+  if (tokenized_source.HasErrors()) {
+    error_stream << "ERROR: Unable to tokenize source file '" << input_file_name
+                 << "'!\n";
+    return false;
+  }
+  tokenized_source.Print(output_stream);
+  return true;
+}
+
+auto Driver::ReportExtraArgs(llvm::StringRef subcommand_text,
+                             llvm::ArrayRef<llvm::StringRef> args) -> void {
+  error_stream << "ERROR: Unexpected additional arguments to the '"
+               << subcommand_text << "' subcommand:";
+  for (auto arg : args) {
+    error_stream << " " << arg;
+  }
+
+  error_stream << "\n";
+}
+
+}  // namespace Carbon

+ 70 - 0
driver/driver.h

@@ -0,0 +1,70 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef DRIVER_DRIVER_H_
+#define DRIVER_DRIVER_H_
+
+#include <cstdint>
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace Carbon {
+
+// Command line interface driver.
+//
+// Provides simple API to parse and run command lines for Carbon.  It is
+// generally expected to be used to implement command line tools for working
+// with the language.
+class Driver {
+ public:
+  // Default constructed driver uses stderr for all error and informational
+  // output.
+  Driver() : output_stream(llvm::outs()), error_stream(llvm::errs()) {}
+
+  // Constructs a driver with any error or informational output directed to a
+  // specified stream.
+  Driver(llvm::raw_ostream& output_stream, llvm::raw_ostream& error_stream)
+      : output_stream(output_stream), error_stream(error_stream) {}
+
+  // Parses the given arguments into both a subcommand to select the operation
+  // to perform and any arguments to that subcommand.
+  //
+  // Returns true if the operation succeeds. If the operation fails, returns
+  // false and any information about the failure is printed to the registered
+  // error stream (stderr by default).
+  auto RunFullCommand(llvm::ArrayRef<llvm::StringRef> args) -> bool;
+
+  // Subcommand that prints available help text to the error stream.
+  //
+  // Optionally one positional parameter may be provided to select a particular
+  // subcommand or detailed section of help to print.
+  //
+  // Returns true if appropriate help text was found and printed. If an invalid
+  // positional parameter (or flag) is provided, returns false.
+  auto RunHelpSubcommand(llvm::ArrayRef<llvm::StringRef> args) -> bool;
+
+  // Subcommand that dumps the token information for the provided source file.
+  //
+  // Requires exactly one positional parameter to designate the source file to
+  // read. May be `-` to read from stdin.
+  //
+  // Returns true if the operation succeeds. If the operation fails, this
+  // returns false and any information about the failure is printed to the
+  // registered error stream (stderr by default).
+  auto RunDumpTokensSubcommand(llvm::ArrayRef<llvm::StringRef> args) -> bool;
+
+ private:
+  auto ReportExtraArgs(llvm::StringRef subcommand_text,
+                       llvm::ArrayRef<llvm::StringRef> args) -> void;
+
+  llvm::raw_ostream& output_stream;
+  llvm::raw_ostream& error_stream;
+};
+
+}  // namespace Carbon
+
+#endif  // DRIVER_DRIVER_H_

+ 82 - 0
driver/driver_fuzzer.cpp

@@ -0,0 +1,82 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <cstdint>
+#include <cstring>
+#include <numeric>
+#include <string>
+
+#include "driver/driver.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace Carbon {
+
+static auto Read(const unsigned char*& data, size_t& size, int& output)
+    -> bool {
+  if (size < sizeof(output)) {
+    return false;
+  }
+  std::memcpy(&output, data, sizeof(output));
+  size -= sizeof(output);
+  data += sizeof(output);
+  return true;
+}
+
+extern "C" auto LLVMFuzzerTestOneInput(const unsigned char* data, size_t size)
+    -> int {
+  // First use the data to compute the number of arguments. Note that for
+  // scaling reasons we don't allow 2^31 arguments, even empty ones. Simply
+  // creating the vector of those won't work. We limit this to 2^20 arguments
+  // total.
+  int num_args;
+  if (!Read(data, size, num_args) || num_args < 0 || num_args > (1 << 20)) {
+    return 0;
+  }
+
+  // Now use the data to compute the length of each argument. We don't want to
+  // exhaust all memory, so bound the search space to using 2^17 bytes of
+  // memory for the argument text itself.
+  size_t arg_length_sum = 0;
+  llvm::SmallVector<int, 16> arg_lengths(num_args);
+  for (int& arg_length : arg_lengths) {
+    if (!Read(data, size, arg_length) || arg_length < 0) {
+      return 0;
+    }
+    arg_length_sum += arg_length;
+    if (arg_length_sum > (1 << 17)) {
+      return 0;
+    }
+  }
+
+  // Ensure we have enough data for all the arguments.
+  if (size < arg_length_sum) {
+    return 0;
+  }
+
+  // Lastly, read the contents of each argument out of the data.
+  llvm::SmallVector<llvm::StringRef, 16> args;
+  args.reserve(num_args);
+  for (int arg_length : arg_lengths) {
+    args.push_back(
+        llvm::StringRef(reinterpret_cast<const char*>(data), arg_length));
+    data += arg_length;
+    size -= arg_length;
+  }
+
+  std::string error_text;
+  llvm::raw_string_ostream error_stream(error_text);
+  llvm::raw_null_ostream output_stream;
+  Driver d(output_stream, error_stream);
+  if (!d.RunFullCommand(args)) {
+    error_stream.flush();
+    if (error_text.find("ERROR:") == std::string::npos) {
+      llvm::errs() << "No error message on a failure!\n";
+      return 1;
+    }
+  }
+  return 0;
+}
+}  // namespace Carbon

+ 21 - 0
driver/driver_main.cpp

@@ -0,0 +1,21 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <cstdlib>
+
+#include "driver/driver.h"
+#include "llvm/ADT/Sequence.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+
+auto main(int argc, char** argv) -> int {
+  if (argc < 1) {
+    return EXIT_FAILURE;
+  }
+
+  llvm::SmallVector<llvm::StringRef, 16> args(argv + 1, argv + argc);
+  Carbon::Driver driver;
+  bool success = driver.RunFullCommand(args);
+  return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 245 - 0
driver/driver_test.cpp

@@ -0,0 +1,245 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "driver/driver.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "lexer/tokenized_buffer_test_helpers.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/YAMLParser.h"
+
+namespace Carbon {
+namespace {
+
+using Carbon::Testing::IsKeyValueScalars;
+using ::testing::Eq;
+using ::testing::HasSubstr;
+using ::testing::NotNull;
+using ::testing::StrEq;
+
+/// A raw_ostream that makes it easy to repeatedly check streamed output.
+class RawTestOstream : public llvm::raw_ostream {
+  std::string buffer;
+
+  void write_impl(const char* ptr, size_t size) override {
+    buffer.append(ptr, ptr + size);
+  }
+
+  [[nodiscard]] auto current_pos() const -> uint64_t override {
+    return buffer.size();
+  }
+
+ public:
+  ~RawTestOstream() override {
+    flush();
+    if (!buffer.empty()) {
+      ADD_FAILURE() << "Unchecked output:\n" << buffer;
+    }
+  }
+
+  /// Flushes the stream and returns the contents so far, clearing the stream
+  /// back to empty.
+  auto TakeStr() -> std::string {
+    flush();
+    std::string result = std::move(buffer);
+    buffer.clear();
+    return result;
+  }
+};
+
+TEST(DriverTest, FullCommandErrors) {
+  RawTestOstream test_output_stream;
+  RawTestOstream test_error_stream;
+  Driver driver = Driver(test_output_stream, test_error_stream);
+
+  EXPECT_FALSE(driver.RunFullCommand({}));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+
+  EXPECT_FALSE(driver.RunFullCommand({"foo"}));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+
+  EXPECT_FALSE(driver.RunFullCommand({"foo --bar --baz"}));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+}
+
+TEST(DriverTest, Help) {
+  RawTestOstream test_output_stream;
+  RawTestOstream test_error_stream;
+  Driver driver = Driver(test_output_stream, test_error_stream);
+
+  EXPECT_TRUE(driver.RunHelpSubcommand({}));
+  EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
+  auto help_text = test_output_stream.TakeStr();
+
+  // Help text should mention each subcommand.
+#define CARBON_SUBCOMMAND(Name, Spelling, ...) \
+  EXPECT_THAT(help_text, HasSubstr(Spelling));
+#include "driver/flags.def"
+
+  // Check that the subcommand dispatch works.
+  EXPECT_TRUE(driver.RunFullCommand({"help"}));
+  EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
+  EXPECT_THAT(test_output_stream.TakeStr(), StrEq(help_text));
+}
+
+TEST(DriverTest, HelpErrors) {
+  RawTestOstream test_output_stream;
+  RawTestOstream test_error_stream;
+  Driver driver = Driver(test_output_stream, test_error_stream);
+
+  EXPECT_FALSE(driver.RunHelpSubcommand({"foo"}));
+  EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+
+  EXPECT_FALSE(driver.RunHelpSubcommand({"help"}));
+  EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+
+  EXPECT_FALSE(driver.RunHelpSubcommand({"--xyz"}));
+  EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+}
+
+auto CreateTestFile(llvm::StringRef text) -> std::string {
+  int fd = -1;
+  llvm::SmallString<1024> path;
+  auto ec = llvm::sys::fs::createTemporaryFile("test_file", ".txt", fd, path);
+  if (ec) {
+    llvm::report_fatal_error(llvm::Twine("Failed to create temporary file: ") +
+                             ec.message());
+  }
+
+  llvm::raw_fd_ostream s(fd, /*shouldClose=*/true);
+  s << text;
+  s.close();
+
+  return path.str().str();
+}
+
+TEST(DriverTest, DumpTokens) {
+  RawTestOstream test_output_stream;
+  RawTestOstream test_error_stream;
+  Driver driver = Driver(test_output_stream, test_error_stream);
+
+  auto test_file_path = CreateTestFile("Hello World");
+  EXPECT_TRUE(driver.RunDumpTokensSubcommand({test_file_path}));
+  EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
+  auto tokenized_text = test_output_stream.TakeStr();
+
+  // Parse the output into a YAML stream. This will print errors to stderr and
+  // is the most stable view of the textual dumping API.
+  llvm::SourceMgr sm;
+  llvm::yaml::Stream yaml_stream(tokenized_text, sm);
+  auto yaml_it = yaml_stream.begin();
+  auto* root_node = llvm::dyn_cast<llvm::yaml::MappingNode>(yaml_it->getRoot());
+  ASSERT_THAT(root_node, NotNull());
+
+  // Walk the top-level mapping of tokens, dig out the sub-mapping of data for
+  // each taken, and then verify those entries.
+  auto mapping_it = llvm::cast<llvm::yaml::MappingNode>(root_node)->begin();
+  auto* token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
+  ASSERT_THAT(token_node, NotNull());
+  auto* token_key_node =
+      llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
+  ASSERT_THAT(token_key_node, NotNull());
+  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
+  auto* token_value_node =
+      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
+  ASSERT_THAT(token_value_node, NotNull());
+  auto token_it = token_value_node->begin();
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "0"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Identifier"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "1"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", "Hello"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("identifier", "0"));
+  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
+
+  ++mapping_it;
+  token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
+  ASSERT_THAT(token_node, NotNull());
+  token_key_node = llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
+  ASSERT_THAT(token_key_node, NotNull());
+  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
+  token_value_node =
+      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
+  ASSERT_THAT(token_value_node, NotNull());
+  token_it = token_value_node->begin();
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "1"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Identifier"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "7"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", "World"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("identifier", "1"));
+  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
+
+  ++mapping_it;
+  token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
+  ASSERT_THAT(token_node, NotNull());
+  token_key_node = llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
+  ASSERT_THAT(token_key_node, NotNull());
+  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
+  token_value_node =
+      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
+  ASSERT_THAT(token_value_node, NotNull());
+  token_it = token_value_node->begin();
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "2"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "EndOfFile"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "12"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
+  ++token_it;
+  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", ""));
+  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
+
+  ASSERT_THAT(++mapping_it, Eq(root_node->end()));
+  ASSERT_THAT(++yaml_it, Eq(yaml_stream.end()));
+
+  // Check that the subcommand dispatch works.
+  EXPECT_TRUE(driver.RunFullCommand({"dump-tokens", test_file_path}));
+  EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
+  EXPECT_THAT(test_output_stream.TakeStr(), StrEq(tokenized_text));
+}
+
+TEST(DriverTest, DumpTokenErrors) {
+  RawTestOstream test_output_stream;
+  RawTestOstream test_error_stream;
+  Driver driver = Driver(test_output_stream, test_error_stream);
+
+  EXPECT_FALSE(driver.RunDumpTokensSubcommand({}));
+  EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+
+  EXPECT_FALSE(driver.RunDumpTokensSubcommand({"--xyz"}));
+  EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+
+  EXPECT_FALSE(driver.RunDumpTokensSubcommand({"/not/a/real/file/name"}));
+  EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
+  EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
+}
+
+}  // namespace
+}  // namespace Carbon

+ 19 - 0
driver/flags.def

@@ -0,0 +1,19 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Note: X-macro header. Do not use include guards.
+
+#ifndef CARBON_SUBCOMMAND
+// FIXME: We should expand `HelpText` to be a short help name, a synopsis, and a
+// long-form description.
+#define CARBON_SUBCOMMAND(Name, Spelling, HelpText)
+#endif
+
+CARBON_SUBCOMMAND(Help, "help",
+                  "Display help information about the driver options.")
+CARBON_SUBCOMMAND(
+    DumpTokens, "dump-tokens",
+    "Dumps the sequence of tokens lexed out of the input source file.")
+
+#undef CARBON_SUBCOMMAND

BIN
driver/fuzzer_corpus/019dd4030b151d2c67da557bf3d56d96dc7839c1


BIN
driver/fuzzer_corpus/02c83534aab9233198863881aa7d82dde0a5b980


BIN
driver/fuzzer_corpus/059a104f98f5658171c48a4d6b0d39036f953264


BIN
driver/fuzzer_corpus/0ab2969c765b7dfd5a132f93c87d60f700089d26


BIN
driver/fuzzer_corpus/11780e1967cb34483305ee0ce43df22b498db8de


BIN
driver/fuzzer_corpus/140803cdfca738e7d23521f13ecf6d87d0bd8980


BIN
driver/fuzzer_corpus/1b65c7f425af22130c7c3aa117961873a194a8ef


BIN
driver/fuzzer_corpus/2251142ebdcd99890424c932de502d094925ad98


BIN
driver/fuzzer_corpus/256dbb574b157de6b00c720c6e123de5d3129e56


BIN
driver/fuzzer_corpus/26305a3c41da25872752f99d86ba0cff5c96c15a


BIN
driver/fuzzer_corpus/285ad940d4ba382b1ca2698edae0f48a132d3f4c


BIN
driver/fuzzer_corpus/2d8c6aebe55a5df4c61804509d3ff1c14978c578


BIN
driver/fuzzer_corpus/2f99d3b7e96de13b21d1b33aa77d5d48fc6e6f88


BIN
driver/fuzzer_corpus/2fd2a7b121a56fdc1e623de01dc1678414adc5dc


BIN
driver/fuzzer_corpus/300ba8e152fe095fcd1f88d18bc64d5b100059e6


BIN
driver/fuzzer_corpus/34c0284640cf11c78b5802ecaf21802ebc83df04


BIN
driver/fuzzer_corpus/3519a9e1095165b91c5d4ba9824d32227da30bd6


BIN
driver/fuzzer_corpus/39294db001ba643d5353c8f418f754c6352c8485


BIN
driver/fuzzer_corpus/3c4c33f72a9ff968e60adf7a2db5206d6375b7c5


BIN
driver/fuzzer_corpus/3c585604e87f855973731fea83e21fab9392d2fc


BIN
driver/fuzzer_corpus/3da89ee273be13437e7ecf760f3fbd4dc0e8d1fe


+ 3 - 0
driver/fuzzer_corpus/3dbc1b67cab9a71bac41d7ee17fa31a8b32a9904

@@ -0,0 +1,3 @@
+
+
+'�

BIN
driver/fuzzer_corpus/4148ee13ed2c74d0f5ce52c46ebd0983c26beb64


BIN
driver/fuzzer_corpus/45f063671e63a4a29f2b05bdd3b6731568119e38


BIN
driver/fuzzer_corpus/4c6f81cf8a9546cd87d1fce006bbe64abd47697b


BIN
driver/fuzzer_corpus/4e0f684219dcb4518aaf4166bd2b51c957ef36de


BIN
driver/fuzzer_corpus/508abb20ddc995fe9e72b69216fd46c24c966358


BIN
driver/fuzzer_corpus/58607edfe3d6ae988025bc9062e244d4c47f54dc


BIN
driver/fuzzer_corpus/58aaf472c4ed651739b9b6debf73437e4eaafb0e


BIN
driver/fuzzer_corpus/596cf9d9a653583c4753d4f51b95e679b63f7c71


BIN
driver/fuzzer_corpus/5dab5e799f3875176eeaa35987db34271c1067a0


BIN
driver/fuzzer_corpus/64e1cd6e55c120c9f2fb329773f345ee6406b74a


BIN
driver/fuzzer_corpus/67cba3df1acabd24c9b66e3c02b819f25f3fcf57


BIN
driver/fuzzer_corpus/6854bbf33c0126b61050dd5fc0ef0126a9701ff0


BIN
driver/fuzzer_corpus/6b03ca7a6835a17b1077f1b98f43c63efd3cac13


BIN
driver/fuzzer_corpus/6ba597db92a7d33adb74499c15e5034ee57cd41b


BIN
driver/fuzzer_corpus/6cd45830db903347e26c3e114ba494052a4a42bd


BIN
driver/fuzzer_corpus/6f3f7dcc0a69d64ed2d99bcee0c0fbb81f970fc6


BIN
driver/fuzzer_corpus/74a21fca37339bc7223b8963473dff771ae964f5


BIN
driver/fuzzer_corpus/78d5bd054d5f82b116ac00b834314d4e24f6c21b


BIN
driver/fuzzer_corpus/79119613c827b0d5bea100613dcd8aa8765c31f6


BIN
driver/fuzzer_corpus/798bd7bd8e4d8d1835ffe6f02fbeb6319f4f106e


BIN
driver/fuzzer_corpus/7bc4f9278deaa7046a5f512c89eac67ebf3ba0d3


BIN
driver/fuzzer_corpus/86179d08482c27dab51fe8a8ea5d8324ef2461e9


+ 3 - 0
driver/fuzzer_corpus/8b0653417087d3d7fd049166a72cd7bcbc5f6dd1

@@ -0,0 +1,3 @@
+ス
+
+*

BIN
driver/fuzzer_corpus/8cabec887fe80b7d9f8b28e989db65a6d72dd22b


BIN
driver/fuzzer_corpus/8eda19d8bfbc4dbd6070bf626cd2f33160ff88a0


BIN
driver/fuzzer_corpus/9069ca78e7450a285173431b3e52c5c25299e473


BIN
driver/fuzzer_corpus/922b5a97efaa1a3ecba58234ad8d01f41d36d313


BIN
driver/fuzzer_corpus/9d47be29ac84966acf09290f8a7df5ba0ac4ea4e


BIN
driver/fuzzer_corpus/a2ae2a7db83b33dc95396607258f553114c9183c


BIN
driver/fuzzer_corpus/a454ca483b4a66b83826d061be2859dd79ff0d6c


BIN
driver/fuzzer_corpus/a5bbebbdf11c537a22a3a9dab4b83498ceb441ca


BIN
driver/fuzzer_corpus/a69f09257d9cd8f5edd9a87b728ab3d75d4352c4


BIN
driver/fuzzer_corpus/a8502f9cfc7efd4ddb3cb771ae00364f60bc279f


+ 1 - 0
driver/fuzzer_corpus/adc83b19e793491b1c6ea0fd8b46cd9f32e592fc

@@ -0,0 +1 @@
+

BIN
driver/fuzzer_corpus/aecfd3909eadc5707dd46249f6239cad4a2d8618


BIN
driver/fuzzer_corpus/af0fb9fda0e123b97ba7be099545a02b8c21a058


BIN
driver/fuzzer_corpus/af1ec8ae5cda07dc979bd68e45d67ffe0d7ffed6


BIN
driver/fuzzer_corpus/b36251f56081e4735b65b1c9d3d765f25ce67a66


BIN
driver/fuzzer_corpus/b62c8e9630f2bb2106ce5717ecf7f10c1b89b958


BIN
driver/fuzzer_corpus/b74fd0148ec8333ad7ce831dd7caa6bc4eff73a9


BIN
driver/fuzzer_corpus/b82f012f0ede708c493207a1eeee577e3c0f884c


BIN
driver/fuzzer_corpus/bb709bdd1d83c039d5c2f02313009030a4f73e0f


BIN
driver/fuzzer_corpus/bc75eaba7c2f73ce991969295463f6af70bfbf1a


BIN
driver/fuzzer_corpus/be0ef7b40ed46b91af0482ce0eb7ad38042b5659


BIN
driver/fuzzer_corpus/beacd06c224890fcf6d9b09b771e1248e241ab4b


BIN
driver/fuzzer_corpus/c349a8a58eb9c57bf1c97e5af483db5144482772


BIN
driver/fuzzer_corpus/c673fa568bbd98569d347b44306c46b50cc2e53f


BIN
driver/fuzzer_corpus/c949526372921c8c5a75ed9e1b198f44fb7a96a1


BIN
driver/fuzzer_corpus/cf294d20a4419644a7716bc1e1783a770c5e2e33


BIN
driver/fuzzer_corpus/cfa74701753e8c5eda1d7364b63b26b667c71bb2


BIN
driver/fuzzer_corpus/d25a8e16325922f85262deec08e77c04acf69f8e


BIN
driver/fuzzer_corpus/d5ebc4110136110c161d48f2d8e53cd1b4edfc0d


BIN
driver/fuzzer_corpus/d97e2e42e26d865d34f68bc92cf8bfe6a40f2eb4


BIN
driver/fuzzer_corpus/e3d8d13265fdbea01751381344b1a7466fb0cdfb


BIN
driver/fuzzer_corpus/e4cd6da031338746e34eb86e8a931d3b589e3d50


BIN
driver/fuzzer_corpus/e8e9adab43ade785323869812e73201de7abf7ad


BIN
driver/fuzzer_corpus/ea21645db01d3810168f14c8dfa601d54d7a52f5


BIN
driver/fuzzer_corpus/ed301991137b18831e72c6a6045fac64e5c010c9


BIN
driver/fuzzer_corpus/efa312d99ad0a086577dfb243164f522aa921b86


BIN
driver/fuzzer_corpus/f488a972b82d572833fccb4600a74d8d4a33ff84


BIN
driver/fuzzer_corpus/f4d6728f4d7cb31739d3ed633a564e4f0015d7cf


BIN
driver/fuzzer_corpus/fb80d614f998904a2fcee5f4ac25ce3e71787364


BIN
driver/fuzzer_corpus/fbd4c32be3552f308d7b77155fe35dab861cf45c


BIN
driver/fuzzer_corpus/fd354923ab6a356b717147ef68e51581e81ec8c8


BIN
driver/fuzzer_corpus/fe0ff78dd1420db832b271cc868019e9945f5f79