diff --git a/backends/p4fmt/CMakeLists.txt b/backends/p4fmt/CMakeLists.txt index 4668e3da89..269a6ded55 100644 --- a/backends/p4fmt/CMakeLists.txt +++ b/backends/p4fmt/CMakeLists.txt @@ -3,6 +3,7 @@ set(FMT_SRCS options.cpp main.cpp p4formatter.cpp + attach.cpp ) set(REFCHECK_SRCS @@ -10,6 +11,7 @@ set(REFCHECK_SRCS options.cpp p4fmt.cpp p4formatter.cpp + attach.cpp ) # p4fmt diff --git a/backends/p4fmt/attach.cpp b/backends/p4fmt/attach.cpp new file mode 100644 index 0000000000..24d0282489 --- /dev/null +++ b/backends/p4fmt/attach.cpp @@ -0,0 +1,68 @@ +#include "backends/p4fmt/attach.h" + +#include "frontends/common/parser_options.h" +#include "lib/source_file.h" + +namespace P4::P4Fmt { + +void Attach::addPrefixComments(NodeId node, const Util::Comment *prefix) { + commentsMap[node].prefix.push_back(prefix); +} + +void Attach::addSuffixComments(NodeId node, const Util::Comment *suffix) { + commentsMap[node].suffix.push_back(suffix); +} + +const Attach::CommentsMap &Attach::getCommentsMap() const { return commentsMap; } + +void Attach::attachCommentsToNode(const IR::Node *node, TraversalType ttype) { + if (node == nullptr || !node->srcInfo.isValid() || processedComments.empty()) { + return; + } + + if (isSystemFile(node->srcInfo.getSourceFile())) { + return; + } + + const auto nodeStart = node->srcInfo.getStart(); + + for (auto &[comment, isAttached] : processedComments) { + // Skip if already attached + if (isAttached) { + continue; + } + + const auto commentEnd = comment->getSourceInfo().getEnd(); + + switch (ttype) { + case TraversalType::Preorder: + if (commentEnd.getLineNumber() == nodeStart.getLineNumber() - 1) { + addPrefixComments(node->clone_id, comment); + isAttached = true; // Mark the comment as attached + } + break; + + case TraversalType::Postorder: + if (commentEnd.getLineNumber() == nodeStart.getLineNumber()) { + addSuffixComments(node->clone_id, comment); + isAttached = true; + } + break; + + default: + P4::error(ErrorType::ERR_INVALID, "traversal type unknown/unsupported."); + return; + } + } +} + +bool Attach::preorder(const IR::Node *node) { + attachCommentsToNode(node, TraversalType::Preorder); + return true; +} + +void Attach::postorder(const IR::Node *node) { + attachCommentsToNode(node, TraversalType::Postorder); +} + +} // namespace P4::P4Fmt diff --git a/backends/p4fmt/attach.h b/backends/p4fmt/attach.h new file mode 100644 index 0000000000..77561d9f29 --- /dev/null +++ b/backends/p4fmt/attach.h @@ -0,0 +1,41 @@ +#ifndef BACKENDS_P4FMT_ATTACH_H_ +#define BACKENDS_P4FMT_ATTACH_H_ + +#include "ir/visitor.h" +#include "lib/source_file.h" + +namespace P4::P4Fmt { + +class Attach : public Inspector { + public: + using NodeId = int; + struct Comments { + std::vector prefix; + std::vector suffix; + }; + using CommentsMap = std::unordered_map; + enum class TraversalType { Preorder, Postorder }; + + explicit Attach(const std::unordered_map &processedComments) + : processedComments(processedComments){}; + + void attachCommentsToNode(const IR::Node *, TraversalType); + + bool preorder(const IR::Node *node) override; + void postorder(const IR::Node *node) override; + + void addPrefixComments(NodeId, const Util::Comment *); + void addSuffixComments(NodeId, const Util::Comment *); + const CommentsMap &getCommentsMap() const; + + private: + /// This Hashmap tracks each comment’s attachment status to IR nodes. Initially, all comments + /// are set to 'false'. + std::unordered_map processedComments; + + CommentsMap commentsMap; +}; + +} // namespace P4::P4Fmt + +#endif /* BACKENDS_P4FMT_ATTACH_H_ */ diff --git a/backends/p4fmt/p4fmt.cpp b/backends/p4fmt/p4fmt.cpp index 72aa602181..4272b1b3e4 100644 --- a/backends/p4fmt/p4fmt.cpp +++ b/backends/p4fmt/p4fmt.cpp @@ -1,15 +1,43 @@ #include "backends/p4fmt/p4fmt.h" +#include "backends/p4fmt/attach.h" +#include "backends/p4fmt/p4formatter.h" #include "frontends/common/parseInput.h" #include "frontends/common/parser_options.h" +#include "frontends/parsers/parserDriver.h" #include "ir/ir.h" #include "lib/compile_context.h" #include "lib/error.h" +#include "lib/source_file.h" #include "options.h" -#include "p4formatter.h" namespace P4::P4Fmt { +std::optional> parseProgram( + const ParserOptions &options) { + if (!std::filesystem::exists(options.file)) { + ::P4::error(ErrorType::ERR_NOT_FOUND, "%1%: No such file found.", options.file); + return std::nullopt; + } + if (options.isv1()) { + ::P4::error(ErrorType::ERR_UNKNOWN, "p4fmt cannot deal with p4-14 programs."); + return std::nullopt; + } + auto preprocessorResult = options.preprocess(); + auto result = + P4ParserDriver::parseProgramSources(preprocessorResult.value().get(), options.file.c_str()); + + if (::P4::errorCount() > 0) { + ::P4::error(ErrorType::ERR_OVERLIMIT, "%1% errors encountered, aborting compilation", + ::P4::errorCount()); + return std::nullopt; + } + + BUG_CHECK(result.first != nullptr, "Parsing failed, but no error was reported"); + + return result; +} + std::stringstream getFormattedOutput(std::filesystem::path inputFile) { AutoCompileContext autoP4FmtContext(new P4Fmt::P4FmtContext); auto &options = P4Fmt::P4FmtContext::get().options(); @@ -18,13 +46,28 @@ std::stringstream getFormattedOutput(std::filesystem::path inputFile) { std::stringstream formattedOutput; - const IR::P4Program *program = P4::parseP4File(options); - if (program == nullptr && ::P4::errorCount() != 0) { - ::P4::error("Failed to parse P4 file."); + auto parseResult = parseProgram(options); + if (!parseResult) { + if (::P4::errorCount() > 0) { + ::P4::error("Failed to parse P4 file."); + } return formattedOutput; } + const auto &[program, sources] = *parseResult; + + std::unordered_map globalCommentsMap; + + // Initialize the global comments map from the list of comments in the program. + if (sources != nullptr) { + for (const auto *comment : sources->getAllComments()) { + globalCommentsMap[comment] = + false; // Initialize all comments as not yet attached to nodes + } + } auto top4 = P4Fmt::P4Formatter(&formattedOutput); + auto attach = P4::P4Fmt::Attach(globalCommentsMap); + program->apply(attach); // Print the program before running front end passes. program->apply(top4); diff --git a/frontends/parsers/parserDriver.cpp b/frontends/parsers/parserDriver.cpp index 96e9d3a2a9..5206e69827 100644 --- a/frontends/parsers/parserDriver.cpp +++ b/frontends/parsers/parserDriver.cpp @@ -153,6 +153,28 @@ bool P4ParserDriver::parse(AbstractP4Lexer &lexer, std::string_view sourceFile, return parse(inputStream.get(), sourceFile, sourceLine); } +/* static */ std::pair +P4ParserDriver::parseProgramSources(std::istream &in, std::string_view sourceFile, + unsigned sourceLine /* = 1 */) { + P4ParserDriver driver; + P4Lexer lexer(in); + if (!driver.parse(lexer, sourceFile, sourceLine)) { + return {nullptr, nullptr}; + } + + auto *program = new IR::P4Program(driver.nodes->srcInfo, *driver.nodes); + const Util::InputSources *sources = driver.sources; + + return {program, sources}; +} + +/*static */ std::pair +P4ParserDriver::parseProgramSources(FILE *in, std::string_view sourceFile, + unsigned sourceLine /* = 1 */) { + AutoStdioInputStream inputStream(in); + return parseProgramSources(inputStream.get(), sourceFile, sourceLine); +} + template const T *P4ParserDriver::parse(P4AnnotationLexer::Type type, const Util::SourceInfo &srcInfo, const IR::Vector &body) { diff --git a/frontends/parsers/parserDriver.h b/frontends/parsers/parserDriver.h index c930794ecb..53d41bd8d5 100644 --- a/frontends/parsers/parserDriver.h +++ b/frontends/parsers/parserDriver.h @@ -115,6 +115,15 @@ class P4ParserDriver final : public AbstractParserDriver { static const IR::P4Program *parse(FILE *in, std::string_view sourceFile, unsigned sourceLine = 1); + /// Parses the input and returns a pair with the P4Program and InputSources. + /// Use this when both the parsed P4Program and InputSources are required, + /// as opposed to the `parse` method, which only returns the P4Program. + static std::pair parseProgramSources( + std::istream &in, std::string_view sourceFile, unsigned sourceLine = 1); + + static std::pair parseProgramSources( + FILE *in, std::string_view sourceFile, unsigned sourceLine = 1); + /** * Parses a P4-16 annotation body. * diff --git a/lib/source_file.cpp b/lib/source_file.cpp index 55a2fe071c..ac44e8fba1 100644 --- a/lib/source_file.cpp +++ b/lib/source_file.cpp @@ -83,6 +83,8 @@ void InputSources::addComment(SourceInfo srcInfo, bool singleLine, cstring body) comments.push_back(new Comment(srcInfo, singleLine, body)); } +const std::vector &InputSources::getAllComments() const { return comments; } + /// prevent further changes void InputSources::seal() { LOG4(toDebugString()); diff --git a/lib/source_file.h b/lib/source_file.h old mode 100644 new mode 100755 index 6c1d6628f4..3958dd8cca --- a/lib/source_file.h +++ b/lib/source_file.h @@ -110,6 +110,7 @@ class SourcePosition final { }; class InputSources; +class Comment; /** Information about the source position of a language element - @@ -246,7 +247,7 @@ struct SourceFileLine { cstring toString() const; }; -class Comment final : IHasDbPrint { +class Comment final : IHasDbPrint, IHasSourceInfo { private: SourceInfo srcInfo; bool singleLine; @@ -255,7 +256,7 @@ class Comment final : IHasDbPrint { public: Comment(SourceInfo srcInfo, bool singleLine, cstring body) : srcInfo(srcInfo), singleLine(singleLine), body(body) {} - cstring toString() const { + cstring toString() const override { std::stringstream str; dbprint(str); return str.str(); @@ -268,6 +269,9 @@ class Comment final : IHasDbPrint { out << body; if (!singleLine) out << "*/"; } + + /// Retrieve the source position associated with this comment. + [[nodiscard]] SourceInfo getSourceInfo() const override { return srcInfo; } }; /** @@ -287,7 +291,6 @@ class InputSources final { public: InputSources(); - std::string_view getLine(unsigned lineNumber) const; /// Original source line that produced the line with the specified number SourceFileLine getSourceLine(unsigned line) const; @@ -319,6 +322,9 @@ class InputSources final { cstring toDebugString() const; void addComment(SourceInfo srcInfo, bool singleLine, cstring body); + /// Returns a list of all the comments found in the file, stored as a part of `InputSources` + const std::vector &getAllComments() const; + private: /// Append this text to the last line; must not contain newlines void appendToLastLine(std::string_view text);