diff --git a/toolchain/driver/BUILD b/toolchain/driver/BUILD index 749d35c112d5..165ef609a2c5 100644 --- a/toolchain/driver/BUILD +++ b/toolchain/driver/BUILD @@ -15,6 +15,7 @@ cc_library( deps = [ "//toolchain/diagnostics:diagnostic_emitter", "//toolchain/lexer:tokenized_buffer", + "//toolchain/parser:parse_tree", "//toolchain/source:source_buffer", "@llvm-project//llvm:Support", ], diff --git a/toolchain/driver/driver.cpp b/toolchain/driver/driver.cpp index 333043b61495..98c321ef4d39 100644 --- a/toolchain/driver/driver.cpp +++ b/toolchain/driver/driver.cpp @@ -13,6 +13,7 @@ #include "llvm/Support/Format.h" #include "toolchain/diagnostics/diagnostic_emitter.h" #include "toolchain/lexer/tokenized_buffer.h" +#include "toolchain/parser/parse_tree.h" #include "toolchain/source/source_buffer.h" namespace Carbon { @@ -118,13 +119,40 @@ auto Driver::RunDumpTokensSubcommand(llvm::ArrayRef args) } auto tokenized_source = TokenizedBuffer::Lex(*source, ConsoleDiagnosticConsumer()); - if (tokenized_source.HasErrors()) { - error_stream << "ERROR: Unable to tokenize source file '" << input_file_name - << "'!\n"; + tokenized_source.Print(output_stream); + return !tokenized_source.HasErrors(); +} + +auto Driver::RunDumpParseTreeSubcommand(llvm::ArrayRef args) + -> bool { + if (args.empty()) { + error_stream << "ERROR: No input file specified.\n"; return false; } - tokenized_source.Print(output_stream); - return true; + + llvm::StringRef input_file_name = args.front(); + args = args.drop_front(); + if (!args.empty()) { + ReportExtraArgs("dump-parse-tree", 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()); + auto parse_tree = + ParseTree::Parse(tokenized_source, ConsoleDiagnosticConsumer()); + parse_tree.Print(output_stream); + return !tokenized_source.HasErrors() && !parse_tree.HasErrors(); } auto Driver::ReportExtraArgs(llvm::StringRef subcommand_text, diff --git a/toolchain/driver/driver.h b/toolchain/driver/driver.h index 93da1b771ec8..d9cecc325726 100644 --- a/toolchain/driver/driver.h +++ b/toolchain/driver/driver.h @@ -57,6 +57,16 @@ class Driver { // registered error stream (stderr by default). auto RunDumpTokensSubcommand(llvm::ArrayRef args) -> bool; + // Subcommand that dumps the parse tree 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 RunDumpParseTreeSubcommand(llvm::ArrayRef args) -> bool; + private: auto ReportExtraArgs(llvm::StringRef subcommand_text, llvm::ArrayRef args) -> void; diff --git a/toolchain/driver/driver_test.cpp b/toolchain/driver/driver_test.cpp index 30a1f5a9a172..7202b41ba63a 100644 --- a/toolchain/driver/driver_test.cpp +++ b/toolchain/driver/driver_test.cpp @@ -179,5 +179,60 @@ TEST(DriverTest, DumpTokenErrors) { EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR")); } +TEST(DriverTest, DumpParseTree) { + RawTestOstream test_output_stream; + RawTestOstream test_error_stream; + Driver driver = Driver(test_output_stream, test_error_stream); + + auto test_file_path = CreateTestFile("var v: Int = 42;"); + EXPECT_TRUE(driver.RunDumpParseTreeSubcommand({test_file_path})); + EXPECT_THAT(test_error_stream.TakeStr(), StrEq("")); + auto tokenized_text = test_output_stream.TakeStr(); + + EXPECT_THAT( + Yaml::Value::FromText(tokenized_text), + ElementsAre(Yaml::SequenceValue{ + Yaml::MappingValue{ + {"node_index", "6"}, + {"kind", "VariableDeclaration"}, + {"text", "var"}, + {"subtree_size", "7"}, + {"children", + Yaml::SequenceValue{ + Yaml::MappingValue{ + {"node_index", "2"}, + {"kind", "PatternBinding"}, + {"text", ":"}, + {"subtree_size", "3"}, + {"children", + Yaml::SequenceValue{ + Yaml::MappingValue{{"node_index", "0"}, + {"kind", "DeclaredName"}, + {"text", "v"}}, + Yaml::MappingValue{{"node_index", "1"}, + {"kind", "NameReference"}, + {"text", "Int"}}}}}, + Yaml::MappingValue{{"node_index", "4"}, + {"kind", "VariableInitializer"}, + {"text", "="}, + {"subtree_size", "2"}, + {"children", // + Yaml::SequenceValue{Yaml::MappingValue{ + {"node_index", "3"}, + {"kind", "Literal"}, + {"text", "42"}}}}}, + Yaml::MappingValue{{"node_index", "5"}, + {"kind", "DeclarationEnd"}, + {"text", ";"}}}}}, + Yaml::MappingValue{{"node_index", "7"}, // + {"kind", "FileEnd"}, + {"text", ""}}})); + + // Check that the subcommand dispatch works. + EXPECT_TRUE(driver.RunFullCommand({"dump-parse-tree", test_file_path})); + EXPECT_THAT(test_error_stream.TakeStr(), StrEq("")); + EXPECT_THAT(test_output_stream.TakeStr(), StrEq(tokenized_text)); +} + } // namespace } // namespace Carbon diff --git a/toolchain/driver/flags.def b/toolchain/driver/flags.def index e4ac6b37defd..39425e2d874f 100644 --- a/toolchain/driver/flags.def +++ b/toolchain/driver/flags.def @@ -15,5 +15,8 @@ CARBON_SUBCOMMAND(Help, "help", CARBON_SUBCOMMAND( DumpTokens, "dump-tokens", "Dumps the sequence of tokens lexed out of the input source file.") +CARBON_SUBCOMMAND( + DumpParseTree, "dump-parse-tree", + "Dumps the parse tree for the input source file.") #undef CARBON_SUBCOMMAND