diff --git a/include/slang/parsing/Lexer.h b/include/slang/parsing/Lexer.h index 439946057..1429b59b1 100644 --- a/include/slang/parsing/Lexer.h +++ b/include/slang/parsing/Lexer.h @@ -35,6 +35,10 @@ struct SLANG_EXPORT LexerOptions { /// If true, the preprocessor will support legacy protected envelope directives, /// for compatibility with old Verilog tools. bool enableLegacyProtect = false; + + /// A flag to enable the interpretation of non-standard line comment pragmas + /// disabling parts of the input for synthesis. + bool enableTranslateOnOffCompat = false; }; /// Possible encodings for encrypted text used in a pragma protect region. @@ -114,6 +118,7 @@ class SLANG_EXPORT Lexer { void scanEncodedText(ProtectEncoding encoding, uint32_t expectedBytes, bool singleLine, bool legacyProtectedMode); void scanProtectComment(); + void scanTranslateOffSection(); template Token create(TokenKind kind, Args&&... args); diff --git a/scripts/diagnostics.txt b/scripts/diagnostics.txt index 40b7f02c8..8887149a8 100644 --- a/scripts/diagnostics.txt +++ b/scripts/diagnostics.txt @@ -35,6 +35,7 @@ error BadOctalDigit "expected octal digit" error BadDecimalDigit "expected decimal digit" error BadHexDigit "expected hexadecimal digit" error TooManyLexerErrors "lexer has encountered too many errors (input is a binary file?)" +error UnclosedTranslateOff "translate_off pragma missing a closing counterpart" warning unknown-escape-code UnknownEscapeCode "unknown character escape sequence '\\\\{}'" warning nonstandard-escape-code NonstandardEscapeCode "non-standard character escape sequence '\\\\{}'" warning invalid-source-encoding InvalidUTF8Seq "invalid UTF-8 sequence in source text" diff --git a/source/parsing/Lexer.cpp b/source/parsing/Lexer.cpp index 6d76df7c9..1d6d8515e 100644 --- a/source/parsing/Lexer.cpp +++ b/source/parsing/Lexer.cpp @@ -27,6 +27,16 @@ static const double BitsPerDecimal = log2(10.0); static constexpr std::string_view PragmaBeginProtected = "pragma protect begin_protected"sv; static constexpr std::string_view PragmaEndProtected = "pragma protect end_protected"sv; +// Note the detection algorithm requires these in alphabetical order; also when a prefix is +// followed by a whitespace in one variant, it's assumed the same prefix will be followed by +// a whitespace in all variants +static std::vector TranslateOffPragmas = { + "pragma synthesis_off"sv, "pragma translate_off"sv, "synopsys synthesis_off"sv, + "synopsys translate_off"sv, "synthesis translate_off"sv, "xilinx translate_off"sv}; +static std::vector TranslateOnPragmas = { + "pragma synthesis_on"sv, "pragma translate_on"sv, "synopsys synthesis_on"sv, + "synopsys translate_on"sv, "synthesis translate_on"sv, "xilinx translate_on"sv}; + namespace slang::parsing { using namespace syntax; @@ -1198,6 +1208,88 @@ void Lexer::scanWhitespace() { addTrivia(TriviaKind::Whitespace); } +bool detectTranslateOnOffPragma(std::string_view view, bool offMode) { + if (view.length() < 2) + return false; + const char *p = view.data() + 2, *end = view.data() + view.size(); + + auto skipWs = [&] { + bool seen = false; + while (p != end && isWhitespace(*p)) { + seen = true; + p++; + } + return seen; + }; + + size_t cpos = 0; + auto clower = offMode ? TranslateOffPragmas.begin() : TranslateOnPragmas.begin(); + auto cupper = offMode ? TranslateOffPragmas.end() : TranslateOnPragmas.end(); + + skipWs(); + while (p != end) { + if ((*clower)[cpos] == ' ') { + if (!skipWs()) + return false; + + cpos++; + } + else { + while (clower < cupper && (*clower)[cpos] < *p) + clower++; + while (cupper > clower && (*(cupper - 1))[cpos] > *p) + cupper--; + + if (clower == cupper) + return false; + + cpos++; + p++; + } + + if (cpos == clower->length()) { + // We have a complete match, check the comment line + // ends there or the match is followed by a whitespace + if (p == end || isWhitespace(*p)) + return true; + return false; + } + } + + return false; +} + +void Lexer::scanTranslateOffSection() { + while (true) { + const char* commentStart = sourceBuffer; + + switch (peek()) { + case '\0': + if (reallyAtEnd()) { + addDiag(diag::UnclosedTranslateOff, currentOffset() - lexemeLength()); + return; + } + break; + case '/': + advance(); + if (peek() == '/') { + advance(); + while (!isNewline(peek()) && !reallyAtEnd()) + advance(); + + std::string_view commentText = + std::string_view(commentStart, (size_t)(sourceBuffer - commentStart)); + if (detectTranslateOnOffPragma(commentText, false)) + return; + } + continue; + default: + break; + } + advance(); + } +} + void Lexer::scanLineComment() { if (options.enableLegacyProtect) { // See if we're looking at a pragma protect comment and skip @@ -1242,6 +1334,15 @@ void Lexer::scanLineComment() { sawUTF8Error |= !scanUTF8Char(sawUTF8Error); } } + + if (options.enableTranslateOnOffCompat) { + if (detectTranslateOnOffPragma(lexeme(), true)) { + scanTranslateOffSection(); + addTrivia(TriviaKind::DisabledText); + return; + } + } + addTrivia(TriviaKind::LineComment); } diff --git a/tests/unittests/parsing/LexerTests.cpp b/tests/unittests/parsing/LexerTests.cpp index 34cae390c..18685d4c2 100644 --- a/tests/unittests/parsing/LexerTests.cpp +++ b/tests/unittests/parsing/LexerTests.cpp @@ -1278,3 +1278,58 @@ TEST_CASE("Hex escape corner case") { CHECK(diagnostics[0].code == diag::InvalidHexEscapeCode); CHECK(diagnostics[1].code == diag::ExpectedClosingQuote); } + +TEST_CASE("Compat translate_on/off pragmas") { + LexerOptions options; + options.enableTranslateOnOffCompat = true; + + auto buffer = getSourceManager().assignText(R"( +a +// pragma synthesis_off +b +// pragma synthesis_on +c +// synthesis translate_off +d +// synthesis translate_off +e +// synthesis translate_on +f +)"sv); + diagnostics.clear(); + Lexer lexer(buffer, alloc, diagnostics, options); + CHECK(diagnostics.empty()); + for (auto& text : {"a"sv, "c"sv, "f"sv}) { + Token tok = lexer.lex(); + REQUIRE(tok.kind == TokenKind::Identifier); + CHECK(!tok.rawText().compare(text)); + } + CHECK(lexer.lex().kind == TokenKind::EndOfFile); +} + +TEST_CASE("Compat translate_on/off pragmas unclosed") { + LexerOptions options; + options.enableTranslateOnOffCompat = true; + + auto buffer = getSourceManager().assignText(R"( +a +// pragma synthesis_off +b +// pragma synthesis_on +c +// synthesis translate_off +d +e +f +)"sv); + diagnostics.clear(); + Lexer lexer(buffer, alloc, diagnostics, options); + for (auto& text : {"a"sv, "c"sv}) { + Token tok = lexer.lex(); + REQUIRE(tok.kind == TokenKind::Identifier); + CHECK(!tok.rawText().compare(text)); + } + CHECK(lexer.lex().kind == TokenKind::EndOfFile); + REQUIRE(diagnostics.size() == 1); + CHECK(diagnostics[0].code == diag::UnclosedTranslateOff); +}