diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 8dbe3ca70..f9b3bd3d6 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -636,7 +636,7 @@ public: puts("vlSymsp->_traceDumpOpen();\n"); } else { puts("VL_PRINTF_MT(\"-Info: "); - puts(protect(nodep->fileline()->filename())); + puts(V3OutFormatter::quoteNameControls(protect(nodep->fileline()->filename()))); puts(":"); puts(cvtToStr(nodep->fileline()->lineno())); puts(": $dumpvar ignored, as Verilated without --trace"); diff --git a/src/V3EmitCMake.cpp b/src/V3EmitCMake.cpp index 6598da9a8..bd065144d 100644 --- a/src/V3EmitCMake.cpp +++ b/src/V3EmitCMake.cpp @@ -43,15 +43,11 @@ class CMakeEmitter final { template static string cmake_list(const List& strs) { string s; - if (strs.begin() != strs.end()) { - s.append("\""); - s.append(VString::quoteAny(*strs.begin(), '"', '\\')); - s.append("\""); - for (typename List::const_iterator it = ++strs.begin(); it != strs.end(); ++it) { - s.append(" \""); - s.append(VString::quoteAny(*it, '"', '\\')); - s.append("\""); - } + for (auto it = strs.begin(); it != strs.end(); ++it) { + s += '"'; + s += V3OutFormatter::quoteNameControls(*it); + s += '"'; + if (it != strs.end()) s += ' '; } return s; } @@ -63,13 +59,13 @@ class CMakeEmitter final { static void cmake_set_raw(std::ofstream& of, const string& name, const string& raw_value, const string& cache_type = "", const string& docstring = "") { of << "set(" << name << " " << raw_value; - if (!cache_type.empty()) of << " CACHE " << cache_type << " \"" << docstring << "\""; + if (!cache_type.empty()) of << " CACHE " << cache_type << " \"" << docstring << '"'; of << ")\n"; } static void cmake_set(std::ofstream& of, const string& name, const string& value, const string& cache_type = "", const string& docstring = "") { - const string raw_value = "\"" + value + "\""; + const string raw_value = '"' + value + '"'; cmake_set_raw(of, name, raw_value, cache_type, docstring); } @@ -88,9 +84,10 @@ class CMakeEmitter final { *of << "# which becomes available after executing `find_package(verilator).\n"; *of << "\n### Constants...\n"; - cmake_set(*of, "PERL", V3Options::getenvPERL(), "FILEPATH", - "Perl executable (from $PERL)"); - cmake_set(*of, "VERILATOR_ROOT", V3Options::getenvVERILATOR_ROOT(), "PATH", + cmake_set(*of, "PERL", V3OutFormatter::quoteNameControls(V3Options::getenvPERL()), + "FILEPATH", "Perl executable (from $PERL)"); + cmake_set(*of, "VERILATOR_ROOT", + V3OutFormatter::quoteNameControls(V3Options::getenvVERILATOR_ROOT()), "PATH", "Path to Verilator kit (from $VERILATOR_ROOT)"); *of << "\n### Compiler flags...\n"; diff --git a/src/V3EmitMk.cpp b/src/V3EmitMk.cpp index 3f38e5119..bdf088c6a 100644 --- a/src/V3EmitMk.cpp +++ b/src/V3EmitMk.cpp @@ -154,9 +154,10 @@ public: } of.puts("\n### Constants...\n"); of.puts("# Perl executable (from $PERL)\n"); - of.puts("PERL = " + V3Options::getenvPERL() + "\n"); + of.puts("PERL = " + V3OutFormatter::quoteNameControls(V3Options::getenvPERL()) + "\n"); of.puts("# Path to Verilator kit (from $VERILATOR_ROOT)\n"); - of.puts("VERILATOR_ROOT = " + V3Options::getenvVERILATOR_ROOT() + "\n"); + of.puts("VERILATOR_ROOT = " + + V3OutFormatter::quoteNameControls(V3Options::getenvVERILATOR_ROOT()) + "\n"); of.puts("# SystemC include directory with systemc.h (from $SYSTEMC_INCLUDE)\n"); of.puts(string{"SYSTEMC_INCLUDE ?= "} + V3Options::getenvSYSTEMC_INCLUDE() + "\n"); of.puts("# SystemC library directory with libsystemc.a (from $SYSTEMC_LIBDIR)\n"); diff --git a/src/V3Error.cpp b/src/V3Error.cpp index 5d915f9c5..1194cd7e3 100644 --- a/src/V3Error.cpp +++ b/src/V3Error.cpp @@ -16,6 +16,7 @@ // clang-format off #include "V3Error.h" +#include "V3Os.h" #ifndef V3ERROR_NO_GLOBAL_ # include "V3Ast.h" # include "V3Global.h" @@ -262,13 +263,8 @@ void V3Error::init() { string V3Error::lineStr(const char* filename, int lineno) VL_PURE { std::ostringstream out; - const char* const fnslashp = std::strrchr(filename, '/'); - if (fnslashp) filename = fnslashp + 1; - out << filename << ":" << std::dec << lineno << ":"; - const char* const spaces = " "; - size_t numsp = out.str().length(); - if (numsp > 20) numsp = 20; - out << (spaces + numsp); + out << V3Os::filenameNonDir(filename) << ":" << std::dec << lineno << ":"; + out << std::string(std::max(0, 20 - static_cast(out.str().length())), ' '); return out.str(); } diff --git a/src/V3File.cpp b/src/V3File.cpp index 157e8d028..7a669f30d 100644 --- a/src/V3File.cpp +++ b/src/V3File.cpp @@ -859,7 +859,7 @@ void V3OutFormatter::putcNoTracking(char chr) { string V3OutFormatter::quoteNameControls(const string& namein, V3OutFormatter::Language lang) VL_PURE { // Encode control chars into output-appropriate escapes - // Reverse is V3Parse::deQuote + // Reverse is VString::unquoteSVString string out; if (lang == LA_XML) { // Encode chars into XML string diff --git a/src/V3FileLine.cpp b/src/V3FileLine.cpp index e97db97b2..a8bf044a6 100644 --- a/src/V3FileLine.cpp +++ b/src/V3FileLine.cpp @@ -203,63 +203,62 @@ string FileLine::xmlDetailedLocation() const { } string FileLine::lineDirectiveStrg(int enterExit) const { - return std::string{"`line "} + cvtToStr(lastLineno()) + " \"" + filename() + "\" " - + cvtToStr(enterExit) + "\n"; + return std::string{"`line "} + cvtToStr(lastLineno()) + " \"" + + V3OutFormatter::quoteNameControls(filename()) + "\" " + cvtToStr(enterExit) + "\n"; } void FileLine::lineDirective(const char* textp, int& enterExitRef) { // Handle `line directive // Does not parse streamNumber/streamLineno as the next input token // will come from the same stream as the previous line. + do { + int lineNo; + // Skip `line + while (*textp && std::isspace(*textp)) ++textp; + while (*textp && !std::isspace(*textp)) ++textp; + while (*textp && std::isspace(*textp)) ++textp; - // Skip `line - while (*textp && std::isspace(*textp)) ++textp; - while (*textp && !std::isspace(*textp)) ++textp; - while (*textp && (std::isspace(*textp) || *textp == '"')) ++textp; + // Grab linenumber + const char* const ln = textp; + while (*textp && !std::isspace(*textp)) ++textp; + if (0 == strncmp(ln, "`__LINE__", textp - ln)) { + // Special case - see docs - don't change other than accounting for `line itself + lineNo = lineno() + 1; + } else if (std::isdigit(*ln)) { + lineNo = std::atoi(ln); + } else { + break; // Fail + } + lineno(lineNo); + while (*textp && (std::isspace(*textp))) ++textp; - // Grab linenumber - bool fail = false; - const char* const ln = textp; - while (*textp && !std::isspace(*textp)) ++textp; - if (0 == strncmp(ln, "`__LINE__", strlen("`__LINE__"))) { - // Special case - see docs - don't change other than accounting for `line itself - lineno(lineno() + 1); - } - if (std::isdigit(*ln)) { - lineno(std::atoi(ln)); - } else { - fail = true; - } - while (*textp && (std::isspace(*textp))) ++textp; - if (*textp != '"') fail = true; - while (*textp && (std::isspace(*textp) || *textp == '"')) ++textp; + // Grab filename + if (*textp != '"') break; // Fail + const char* const fn = ++textp; + while (*textp && *textp != '"') ++textp; + if (*textp != '"') break; // Fail + string errMsg; + const string& parsedFilename = VString::unquoteSVString(string{fn, textp}, errMsg); + if (!errMsg.empty()) this->v3error(errMsg.c_str()); + filename(parsedFilename); + ++textp; + while (*textp && std::isspace(*textp)) ++textp; - // Grab filename - const char* const fn = textp; - while (*textp && !(std::isspace(*textp) || *textp == '"')) ++textp; - if (textp != fn) { - string strfn = fn; - strfn = strfn.substr(0, textp - fn); - filename(strfn); - } else { - fail = true; - } + // Grab level + if (!std::isdigit(*textp)) break; // Fail + const int level = std::atoi(textp); + if (level < 0 || level >= 3) break; // Fail + /// TODO: store lineno/filename only when the `line directive is valid + /// lineno(lineNo); + /// filename(filenameNew); + enterExitRef = level; + return; + } while (false); - // Grab level - while (*textp && (std::isspace(*textp) || *textp == '"')) ++textp; - if (std::isdigit(*textp)) { - enterExitRef = std::atoi(textp); - if (enterExitRef >= 3) fail = true; - } else { - enterExitRef = 0; - fail = true; - } - - if (fail && v3Global.opt.pedantic()) { - v3error("`line was not properly formed with '`line number \"filename\" level'\n"); - } - - // printf ("PPLINE %d '%s'\n", s_lineno, s_filename.c_str()); + // Fail + // TODO: show correct place of the code + v3error("`line was not properly formed with '`line number \"filename\" level'\n"); + enterExitRef = 0; } void FileLine::forwardToken(const char* textp, size_t size, bool trackLines) { @@ -293,12 +292,7 @@ FileLine* FileLine::copyOrSameFileLine() { string FileLine::filebasename() const VL_MT_SAFE { return V3Os::filenameNonDir(filename()); } -string FileLine::filebasenameNoExt() const { - string name = filebasename(); - string::size_type pos; - if ((pos = name.find('.')) != string::npos) name = name.substr(0, pos); - return name; -} +string FileLine::filebasenameNoExt() const { return V3Os::filenameNonDirExt(filename()); } string FileLine::firstColumnLetters() const VL_MT_SAFE { const char a = ((firstColumn() / 26) % 26) + 'a'; diff --git a/src/V3Options.cpp b/src/V3Options.cpp index 42e33a040..9b816f63d 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -81,19 +81,21 @@ public: // ACCESSOR METHODS void addIncDirUser(const string& incdir) { - const auto itFoundPair = m_incDirUserSet.insert(incdir); + const string& dir = V3Os::filenameCleanup(incdir); + const auto itFoundPair = m_incDirUserSet.insert(dir); if (itFoundPair.second) { // cppcheck-suppress stlFindInsert // cppcheck 1.90 bug - m_incDirUsers.push_back(incdir); - m_incDirFallbacks.remove(incdir); // User has priority over Fallback - m_incDirFallbackSet.erase(incdir); // User has priority over Fallback + m_incDirUsers.push_back(dir); + m_incDirFallbacks.remove(dir); // User has priority over Fallback + m_incDirFallbackSet.erase(dir); // User has priority over Fallback } } void addIncDirFallback(const string& incdir) { - if (m_incDirUserSet.find(incdir) + const string& dir = V3Os::filenameCleanup(incdir); + if (m_incDirUserSet.find(dir) == m_incDirUserSet.end()) { // User has priority over Fallback - const auto itFoundPair = m_incDirFallbackSet.insert(incdir); - if (itFoundPair.second) m_incDirFallbacks.push_back(incdir); + const auto itFoundPair = m_incDirFallbackSet.insert(dir); + if (itFoundPair.second) m_incDirFallbacks.push_back(dir); } } void addLangExt(const string& langext, const V3LangCode& lc) { @@ -508,20 +510,16 @@ string V3Options::fileExists(const string& filename) { return ""; // Not found } // Check if it is a directory, ignore if so - string filenameOut = V3Os::filenameFromDirBase(dir, basename); + string filenameOut = V3Os::filenameJoin(dir, basename); if (!fileStatNormal(filenameOut)) return ""; // Directory return filenameOut; } string V3Options::filePathCheckOneDir(const string& modname, const string& dirname) { for (const string& i : m_impp->m_libExtVs) { - const string fn = V3Os::filenameFromDirBase(dirname, modname + i); + const string fn = V3Os::filenameJoin(dirname, modname + i); string exists = fileExists(fn); - if (exists != "") { - // Strip ./, it just looks ugly - if (exists.substr(0, 2) == "./") exists.erase(0, 2); - return exists; - } + if (exists != "") return exists; } return ""; } @@ -548,29 +546,30 @@ string V3Options::filePath(FileLine* fl, const string& modname, const string& la // Find a filename to read the specified module name, // using the incdir and libext's. // Return "" if not found. - if (!V3Os::filenameIsRel(modname)) { - // modname is an absolute path, so can find getStdPackagePath() - string exists = filePathCheckOneDir(modname, ""); + const string filename = V3Os::filenameCleanup(modname); + if (!V3Os::filenameIsRel(filename)) { + // filename is an absolute path, so can find getStdPackagePath() + string exists = filePathCheckOneDir(filename, ""); if (exists != "") return exists; } for (const string& dir : m_impp->m_incDirUsers) { - string exists = filePathCheckOneDir(modname, dir); + string exists = filePathCheckOneDir(filename, dir); if (exists != "") return exists; } for (const string& dir : m_impp->m_incDirFallbacks) { - string exists = filePathCheckOneDir(modname, dir); + string exists = filePathCheckOneDir(filename, dir); if (exists != "") return exists; } if (m_relativeIncludes) { - const string exists = filePathCheckOneDir(modname, lastpath); + const string exists = filePathCheckOneDir(filename, lastpath); if (exists != "") return V3Os::filenameRealPath(exists); } // Warn and return not found if (errmsg != "") { - fl->v3error(errmsg + modname); - filePathLookedMsg(fl, modname); + fl->v3error(errmsg + filename); + filePathLookedMsg(fl, filename); } return ""; } @@ -592,13 +591,13 @@ void V3Options::filePathLookedMsg(FileLine* fl, const string& modname) { std::cerr << V3Error::warnMoreStandalone() << "... Looked in:" << endl; for (const string& dir : m_impp->m_incDirUsers) { for (const string& ext : m_impp->m_libExtVs) { - const string fn = V3Os::filenameFromDirBase(dir, modname + ext); + const string fn = V3Os::filenameJoin(dir, modname + ext); std::cerr << V3Error::warnMoreStandalone() << " " << fn << endl; } } for (const string& dir : m_impp->m_incDirFallbacks) { for (const string& ext : m_impp->m_libExtVs) { - const string fn = V3Os::filenameFromDirBase(dir, modname + ext); + const string fn = V3Os::filenameJoin(dir, modname + ext); std::cerr << V3Error::warnMoreStandalone() << " " << fn << endl; } } @@ -657,7 +656,7 @@ string V3Options::getenvMAKEFLAGS() { // } string V3Options::getenvPERL() { // - return V3Os::getenvStr("PERL", "perl"); + return V3Os::filenameCleanup(V3Os::getenvStr("PERL", "perl")); } string V3Options::getenvSYSTEMC() { @@ -669,7 +668,7 @@ string V3Options::getenvSYSTEMC() { var = defenv; V3Os::setenvStr("SYSTEMC", var, "Hardcoded at build time"); } - return var; + return V3Os::filenameCleanup(var); } string V3Options::getenvSYSTEMC_ARCH() { @@ -718,9 +717,9 @@ string V3Options::getenvSYSTEMC_INCLUDE() { } if (var == "") { const string sc = getenvSYSTEMC(); - if (sc != "") var = sc + "/include"; + if (sc != "") var = V3Os::filenameJoin(sc, "include"); } - return var; + return V3Os::filenameCleanup(var); } string V3Options::getenvSYSTEMC_LIBDIR() { @@ -735,9 +734,9 @@ string V3Options::getenvSYSTEMC_LIBDIR() { if (var == "") { const string sc = getenvSYSTEMC(); const string arch = getenvSYSTEMC_ARCH(); - if (sc != "" && arch != "") var = sc + "/lib-" + arch; + if (sc != "" && arch != "") var = V3Os::filenameJoin(sc, "lib-" + arch); } - return var; + return V3Os::filenameCleanup(var); } string V3Options::getenvVERILATOR_ROOT() { @@ -750,11 +749,11 @@ string V3Options::getenvVERILATOR_ROOT() { V3Os::setenvStr("VERILATOR_ROOT", var, "Hardcoded at build time"); } if (var == "") v3fatal("$VERILATOR_ROOT needs to be in environment\n"); - return var; + return V3Os::filenameCleanup(var); } string V3Options::getStdPackagePath() { - return getenvVERILATOR_ROOT() + "/include/verilated_std.sv"; + return V3Os::filenameJoin(getenvVERILATOR_ROOT(), "include", "verilated_std.sv"); } string V3Options::getSupported(const string& var) { @@ -1890,7 +1889,8 @@ void V3Options::parseOptsFile(FileLine* fl, const string& filename, bool rel) VL string V3Options::parseFileArg(const string& optdir, const string& relfilename) { string filename = V3Os::filenameSubstitute(relfilename); - if (optdir != "." && V3Os::filenameIsRel(filename)) filename = optdir + "/" + filename; + if (optdir != "." && V3Os::filenameIsRel(filename)) + filename = V3Os::filenameJoin(optdir, filename); return filename; } diff --git a/src/V3Os.cpp b/src/V3Os.cpp index 01d9db383..8763725c1 100644 --- a/src/V3Os.cpp +++ b/src/V3Os.cpp @@ -124,15 +124,46 @@ void V3Os::setenvStr(const string& envvar, const string& value, const string& wh //###################################################################### // Generic filename utilities -static bool isSlash(char ch) VL_PURE { return ch == '/' || ch == '\\'; } +#if defined(_WIN32) || defined(__MINGW32__) +static constexpr char V3OS_SLASH = '\\'; +#else +static constexpr char V3OS_SLASH = '/'; +#endif -string V3Os::filenameFromDirBase(const string& dir, const string& basename) VL_PURE { - // Don't return ./{filename} because if filename was absolute, that makes it relative - if (dir.empty() || dir == ".") { - return basename; - } else { - return dir + "/" + basename; +static bool isSlash(char ch) VL_PURE { +#if defined(_WIN32) || defined(__MINGW32__) + return ch == '/' || ch == '\\'; +#else + return ch == '/'; +#endif +} + +string V3Os::filenameCleanup(const string& filename) VL_PURE { + string str; + str.reserve(filename.length()); + bool lastIsSlash = false; + for (const char ch : filename) { + const bool lastIsSlashOld = lastIsSlash; + lastIsSlash = isSlash(ch); + if (lastIsSlash && lastIsSlashOld) continue; + str += ch; } + if (str.size() > 1 && isSlash(str.back())) str.pop_back(); + while (str.size() > 2 && str[0] == '.' && isSlash(str[1])) str.erase(0, 2); + return str; +} + +string V3Os::filenameJoin(std::initializer_list paths) VL_PURE { + string fullpath; + for (const auto& item : paths) { + if (item.empty() || item == ".") { + continue; + } else { + if (!fullpath.empty()) fullpath += V3OS_SLASH; + fullpath += item; + } + } + return fullpath; } string V3Os::filenameDir(const string& filename) VL_PURE { @@ -144,7 +175,7 @@ string V3Os::filenameDir(const string& filename) VL_PURE { if (it.base() == filename.begin()) { return "."; } else { - return {filename.begin(), (++it).base()}; + return string{filename.begin(), (++it).base()}; } } @@ -164,6 +195,10 @@ string V3Os::filenameNonExt(const string& filename) VL_PURE { return base; } +string V3Os::filenameNonDirExt(const string& filename) VL_PURE { + return filenameNonExt(filenameNonDir(filename)); +} + string V3Os::filenameSubstitute(const string& filename) { string result; // cppcheck-has-bug-suppress unusedLabel @@ -392,3 +427,27 @@ int V3Os::system(const string& command) { return exit_code; } } + +void V3Os::selfTest() { +#ifdef VL_DEBUG + UASSERT_SELFTEST(string, filenameCleanup(""), ""); + UASSERT_SELFTEST(string, filenameCleanup("."), "."); + UASSERT_SELFTEST(string, filenameCleanup(".."), ".."); + UASSERT_SELFTEST(string, filenameCleanup("/"), "/"); + UASSERT_SELFTEST(string, filenameCleanup("../"), ".."); + UASSERT_SELFTEST(string, filenameCleanup("//"), "/"); + UASSERT_SELFTEST(string, filenameCleanup("//."), "/."); + UASSERT_SELFTEST(string, filenameCleanup("./"), "."); + UASSERT_SELFTEST(string, filenameCleanup("././"), "."); + UASSERT_SELFTEST(string, filenameCleanup(".///"), "."); + UASSERT_SELFTEST(string, filenameCleanup("a"), "a"); + UASSERT_SELFTEST(string, filenameCleanup("a/"), "a"); + UASSERT_SELFTEST(string, filenameCleanup("a/b"), "a/b"); + UASSERT_SELFTEST(string, filenameCleanup("././//./a/b"), "a/b"); + UASSERT_SELFTEST(string, filenameCleanup(".//./a///"), "a"); + UASSERT_SELFTEST(string, filenameCleanup("///a/./b///."), "/a/./b/."); + UASSERT_SELFTEST(string, filenameCleanup("aaa/bbb/ccc/"), "aaa/bbb/ccc"); + UASSERT_SELFTEST(string, filenameCleanup("./aaa/bbb/ccc/"), "aaa/bbb/ccc"); + UASSERT_SELFTEST(string, filenameCleanup("../aaa/bbb/ccc/"), "../aaa/bbb/ccc"); +#endif +} diff --git a/src/V3Os.h b/src/V3Os.h index da1bcb1d0..3bf69ca17 100644 --- a/src/V3Os.h +++ b/src/V3Os.h @@ -35,22 +35,28 @@ public: static void setenvStr(const string& envvar, const string& value, const string& why); // METHODS (generic filename utilities) - static string filenameFromDirBase(const string& dir, const string& basename) VL_PURE; - ///< Return non-directory part of filename + ///< @return concatenated path + static string filenameJoin(std::initializer_list paths) VL_PURE; + template + static string filenameJoin(Args... args) VL_PURE { + return filenameJoin({args...}); + }; + ///< @return file path without repeated separators and ./ prefix + static string filenameCleanup(const string& filename) VL_PURE; + ///< @return non-directory part of filename static string filenameNonDir(const string& filename) VL_PURE; - ///< Return non-extensioned (no .) part of filename + ///< @return non-extensioned (no .) part of filename static string filenameNonExt(const string& filename) VL_PURE; - ///< Return basename of filename - static string filenameNonDirExt(const string& filename) VL_PURE { - return filenameNonExt(filenameNonDir(filename)); - } - ///< Return directory part of filename + ///< @return basename of filename + static string filenameNonDirExt(const string& filename) VL_PURE; + ///< @return directory part of filename static string filenameDir(const string& filename) VL_PURE; - /// Return filename with env vars removed + ///< @return filename with env vars removed static string filenameSubstitute(const string& filename); - ///< Return realpath of filename + ///< @return realpath of filename static string filenameRealPath(const string& filename) VL_PURE; - static bool filenameIsRel(const string& filename) VL_PURE; ///< True if relative + ///< @return filename is relative + static bool filenameIsRel(const string& filename) VL_PURE; // METHODS (file utilities) static string getline(std::istream& is, char delim = '\n'); @@ -72,6 +78,7 @@ public: // METHODS (sub command) /// Run system command, returns the exit code of the child process. static int system(const string& command); + static void selfTest(); }; #endif // Guard diff --git a/src/V3ParseGrammar.cpp b/src/V3ParseGrammar.cpp index 10be43782..384ec1ffa 100644 --- a/src/V3ParseGrammar.cpp +++ b/src/V3ParseGrammar.cpp @@ -251,63 +251,9 @@ AstVar* V3ParseGrammar::createVariable(FileLine* fileline, const string& name, return nodep; } -string V3ParseGrammar::deQuote(FileLine* fileline, string text) { - // Fix up the quoted strings the user put in, for example "\"" becomes " - // Reverse is V3OutFormatter::quoteNameControls(...) - bool quoted = false; - string newtext; - unsigned char octal_val = 0; - int octal_digits = 0; - for (string::const_iterator cp = text.begin(); cp != text.end(); ++cp) { - if (quoted) { - if (std::isdigit(*cp)) { - octal_val = octal_val * 8 + (*cp - '0'); - if (++octal_digits == 3) { - octal_digits = 0; - quoted = false; - newtext += octal_val; - } - } else { - if (octal_digits) { - // Spec allows 1-3 digits - octal_digits = 0; - quoted = false; - newtext += octal_val; - --cp; // Backup to reprocess terminating character as non-escaped - continue; - } - quoted = false; - if (*cp == 'n') { - newtext += '\n'; - } else if (*cp == 'a') { - newtext += '\a'; // SystemVerilog 3.1 - } else if (*cp == 'f') { - newtext += '\f'; // SystemVerilog 3.1 - } else if (*cp == 'r') { - newtext += '\r'; - } else if (*cp == 't') { - newtext += '\t'; - } else if (*cp == 'v') { - newtext += '\v'; // SystemVerilog 3.1 - } else if (*cp == 'x' && std::isxdigit(cp[1]) - && std::isxdigit(cp[2])) { // SystemVerilog 3.1 -#define vl_decodexdigit(c) ((std::isdigit(c) ? ((c) - '0') : (std::tolower((c)) - 'a' + 10))) - newtext - += static_cast(16 * vl_decodexdigit(cp[1]) + vl_decodexdigit(cp[2])); - cp += 2; - } else if (std::isalnum(*cp)) { - fileline->v3error("Unknown escape sequence: \\" << *cp); - break; - } else { - newtext += *cp; - } - } - } else if (*cp == '\\') { - quoted = true; - octal_digits = 0; - } else { - newtext += *cp; - } - } - return newtext; +string V3ParseGrammar::unquoteString(FileLine* fileline, string text) { + string errMsg; + string res = VString::unquoteSVString(text, errMsg); + if (!errMsg.empty()) fileline->v3error(errMsg.c_str()); + return res; } diff --git a/src/V3PreProc.cpp b/src/V3PreProc.cpp index 075d677d2..7769dd41a 100644 --- a/src/V3PreProc.cpp +++ b/src/V3PreProc.cpp @@ -358,7 +358,7 @@ string V3PreProcImp::removeDefines(const string& text) { string rtnsym = text; for (int loopprevent = 0; loopprevent < 100; loopprevent++) { string xsym = rtnsym; - if (xsym.substr(0, 1) == "`") xsym.replace(0, 1, ""); + if (xsym[0] == '`') xsym.erase(0, 1); if (defExists(xsym)) { val = defValue(xsym); if (val != rtnsym) { diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 32386e38b..b3849955d 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -663,7 +663,8 @@ std::pair makeEvalLoop(AstNetlist* netlistp, const s newcallp->dtypeSetVoid(); blockp->addNodesp(newcallp->makeStmt()); add("#endif\n"); - add("VL_FATAL_MT(\"" + file + "\", " + line + ", \"\", "); + add("VL_FATAL_MT(\"" + V3OutFormatter::quoteNameControls(file) + "\", " + line + + ", \"\", "); add("\"" + name + " region did not converge.\");\n"); } diff --git a/src/V3String.cpp b/src/V3String.cpp index 5ed5279e1..c23a15705 100644 --- a/src/V3String.cpp +++ b/src/V3String.cpp @@ -20,6 +20,7 @@ #include "V3String.h" #include "V3Error.h" +#include "V3FileLine.h" #ifndef V3ERROR_NO_GLOBAL_ #include "V3Global.h" @@ -130,6 +131,70 @@ string VString::escapeStringForPath(const string& str) { return result; } +static int vl_decodexdigit(char c) { + return std::isdigit(c) ? c - '0' : std::tolower(c) - 'a' + 10; +} + +string VString::unquoteSVString(const string& text, string& errOut) { + bool quoted = false; + string newtext; + newtext.reserve(text.size()); + unsigned char octal_val = 0; + int octal_digits = 0; + for (string::const_iterator cp = text.begin(); cp != text.end(); ++cp) { + if (quoted) { + if (std::isdigit(*cp)) { + octal_val = octal_val * 8 + (*cp - '0'); + if (++octal_digits == 3) { + octal_digits = 0; + quoted = false; + newtext += octal_val; + } + } else { + if (octal_digits) { + // Spec allows 1-3 digits + octal_digits = 0; + quoted = false; + newtext += octal_val; + --cp; // Backup to reprocess terminating character as non-escaped + continue; + } + quoted = false; + if (*cp == 'n') { + newtext += '\n'; + } else if (*cp == 'a') { + newtext += '\a'; // SystemVerilog 3.1 + } else if (*cp == 'f') { + newtext += '\f'; // SystemVerilog 3.1 + } else if (*cp == 'r') { + newtext += '\r'; + } else if (*cp == 't') { + newtext += '\t'; + } else if (*cp == 'v') { + newtext += '\v'; // SystemVerilog 3.1 + } else if (*cp == 'x' && std::isxdigit(cp[1]) + && std::isxdigit(cp[2])) { // SystemVerilog 3.1 + newtext + += static_cast(16 * vl_decodexdigit(cp[1]) + vl_decodexdigit(cp[2])); + cp += 2; + } else if (std::isalnum(*cp)) { + errOut = "Unknown escape sequence: \\"; + errOut += *cp; + break; + } else { + newtext += *cp; + } + } + } else if (*cp == '\\') { + quoted = true; + octal_digits = 0; + } else { + newtext += *cp; + } + } + return newtext; +} + string VString::spaceUnprintable(const string& str) VL_PURE { string result; for (const char c : str) { diff --git a/src/V3String.h b/src/V3String.h index cbca5de56..33c792757 100644 --- a/src/V3String.h +++ b/src/V3String.h @@ -102,6 +102,9 @@ public: // Surround a raw string by double quote and escape if necessary // e.g. input abc's becomes "\"abc\'s\"" static string escapeStringForPath(const string& str); + // Convert SV quoted string input from source code to normal form. + // Reverse is V3OutFormatter::quoteNameControls(...) + static string unquoteSVString(const string& text, string& errOut); // Escape path in Windows // e.g. input `C:\Program Files\My Program\My Program.exe` becomes // `C:\\Program\ Files\\My\ Program\\My\ Program.exe` diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 10b0fb06d..cb2109ea0 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -642,6 +642,7 @@ static void verilate(const string& argString) { { const V3MtDisabledLockGuard mtDisabler{v3MtDisabledLock()}; + V3Os::selfTest(); VHashSha256::selfTest(); VSpellCheck::selfTest(); V3Graph::selfTest(); @@ -782,7 +783,7 @@ int main(int argc, char** argv) { V3PreShell::boot(); // Command option parsing - v3Global.opt.buildDepBin(VString::escapeStringForPath(argv[0])); + v3Global.opt.buildDepBin(V3Os::filenameCleanup(argv[0])); v3Global.opt.parseOpts(new FileLine{FileLine::commandLineFilename()}, argc - 1, argv + 1); // Validate settings (aka Boost.Program_options) diff --git a/src/verilog.y b/src/verilog.y index 2b9120969..39f2e31ff 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -129,7 +129,7 @@ public: AstNode* attrsp) VL_MT_DISABLED; AstNode* createSupplyExpr(FileLine* fileline, const string& name, int value) VL_MT_DISABLED; AstText* createTextQuoted(FileLine* fileline, const string& text) { - string newtext = deQuote(fileline, text); + string newtext = GRAMMARP->unquoteString(fileline, text); return new AstText{fileline, newtext}; } AstNode* createCellOrIfaceRef(FileLine* fileline, const string& name, AstPin* pinlistp, @@ -253,7 +253,7 @@ public: return createArray(dtypep, rangearraysp, isPacked); } } - string deQuote(FileLine* fileline, string text) VL_MT_DISABLED; + string unquoteString(FileLine* fileline, string text) VL_MT_DISABLED; void checkDpiVer(FileLine* fileline, const string& str) { if (str != "DPI-C" && !v3Global.opt.bboxSys()) { fileline->v3error("Unsupported DPI type '" << str << "': Use 'DPI-C'"); @@ -5675,13 +5675,13 @@ parseRefBase: // yaSTRING shouldn't be used directly, instead via an abstraction below str: // yaSTRING but with \{escapes} need decoded - yaSTRING { $$ = PARSEP->newString(GRAMMARP->deQuote($1, *$1)); } + yaSTRING { $$ = PARSEP->newString(GRAMMARP->unquoteString($1, *$1)); } ; strAsInt: yaSTRING { // Numeric context, so IEEE 1800-2017 11.10.3 "" is a "\000" - $$ = new AstConst{$1, AstConst::VerilogStringLiteral{}, GRAMMARP->deQuote($1, *$1)}; } + $$ = new AstConst{$1, AstConst::VerilogStringLiteral{}, GRAMMARP->unquoteString($1, *$1)}; } ; strAsIntIgnore: // strAsInt, but never matches for when expr shouldn't parse strings diff --git a/test_regress/t/t_pp_line.out b/test_regress/t/t_pp_line.out new file mode 100644 index 000000000..a3e32422f --- /dev/null +++ b/test_regress/t/t_pp_line.out @@ -0,0 +1,16 @@ +-Info: some file:100:1: aaaaaaaa + : ... note: In instance 't' + 100 | $info("aaaaaaaa"); + | ^~~~~ +-Info: some file:101:1: bbbbbbbb + : ... note: In instance 't' + 101 | $info("bbbbbbbb"); + | ^~~~~ +-Info: somefile.v:200:1: cccccccc + : ... note: In instance 't' + 200 | $info("cccccccc"); + | ^~~~~ +-Info: /a/somefile.v:300:1: dddddddd + : ... note: In instance 't' + 300 | $info("dddddddd"); + | ^~~~~ diff --git a/test_regress/t/t_pp_line.pl b/test_regress/t/t_pp_line.pl new file mode 100755 index 000000000..d1ed9a923 --- /dev/null +++ b/test_regress/t/t_pp_line.pl @@ -0,0 +1,18 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2003 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(linter => 1); + +lint( + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_pp_line.v b/test_regress/t/t_pp_line.v new file mode 100644 index 000000000..ac991e550 --- /dev/null +++ b/test_regress/t/t_pp_line.v @@ -0,0 +1,15 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2023 by Anthony Donlon. +// SPDX-License-Identifier: CC0-1.0 + +module t; +`line 100 "some file" 0 +$info("aaaaaaaa"); +$info("bbbbbbbb"); +`line 200 "somefile.v" 0 +$info("cccccccc"); +`line 300 "/a/somefile.v" 0 +$info("dddddddd"); +endmodule diff --git a/test_regress/t/t_pp_line_bad.out b/test_regress/t/t_pp_line_bad.out index 062cbf6af..8365b61c6 100644 --- a/test_regress/t/t_pp_line_bad.out +++ b/test_regress/t/t_pp_line_bad.out @@ -1,13 +1,22 @@ %Error: t/t_pp_line_bad.v:100:1: `line was not properly formed with '`line number "filename" level' 100 | `line 100 | ^ -%Error: somefile:100:1: `line was not properly formed with '`line number "filename" level' - 100 | `line 100 +%Error: t/t_pp_line_bad.v:200:1: `line was not properly formed with '`line number "filename" level' + 200 | `line 100 + | ^ +%Error: t/t_pp_line_bad.v:300:1: `line was not properly formed with '`line number "filename" level' + 300 | `line 100 + | ^ +%Error: some file:400:1: `line was not properly formed with '`line number "filename" level' + 400 | `line 100 + | ^ +%Error: somefile:500:1: `line was not properly formed with '`line number "filename" level' + 500 | `line 100 + | ^ +%Error: some file:600:1: `line was not properly formed with '`line number "filename" level' + 600 | `line 100 | ^ -%Error: somefile:100:1: `line was not properly formed with '`line number "filename" level' %Error: t/t_pp_line_bad.v:7:1: Define or directive not defined: '`line' 7 | `line | ^~~~~ -%Error: somefile:100:1: `line was not properly formed with '`line number "filename" level' - t/t_pp_line_bad.v:101:1: ... note: In file included from 't_pp_line_bad.v' %Error: Exiting due to diff --git a/test_regress/t/t_pp_line_bad.pl b/test_regress/t/t_pp_line_bad.pl index 9de6b85ee..a60503a1f 100755 --- a/test_regress/t/t_pp_line_bad.pl +++ b/test_regress/t/t_pp_line_bad.pl @@ -8,10 +8,9 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -scenarios(vlt => 1); +scenarios(linter => 1); lint( - verilator_flags2 => ["-Wpedantic"], fails => 1, expect_filename => $Self->{golden_filename}, ); diff --git a/test_regress/t/t_pp_line_bad.v b/test_regress/t/t_pp_line_bad.v index e1ac808a4..0d41247a1 100644 --- a/test_regress/t/t_pp_line_bad.v +++ b/test_regress/t/t_pp_line_bad.v @@ -6,6 +6,8 @@ `line `line 100 -`line 100 somefile 1 -`line 100 "somefile" -`line 100 "somefile" 3 +`line 200 somefile +`line 300 "somefile 1 +`line 400 "some file" +`line 500 "somefile" 3 +`line 600 "some file" 3