diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 05976791d..7e8563aa8 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -54,7 +54,7 @@ read_param_card (tl::Extractor &ex, const db::Netlist *netlist, std::mapnormalize_name (n); + return netlist->normalize_name (NetlistSpiceReader::unescape_name (n)); } class SpiceCircuitDict @@ -610,7 +610,7 @@ SpiceCircuitDict::get_line () std::string path_or_libname; ex.read_word_or_quoted (path_or_libname, allowed_name_chars); - if (! ex.at_end ()) { + if (! NetlistSpiceReader::at_eol (ex)) { std::string libname; ex.read_word_or_quoted (libname, allowed_name_chars); @@ -648,7 +648,7 @@ SpiceCircuitDict::get_line () ex.expect_end (); - } else if (ex.at_end () || ex.test ("*")) { + } else if (NetlistSpiceReader::at_eol (ex)) { // skip empty and comment lines @@ -680,7 +680,7 @@ SpiceCircuitDict::read_card () } else if (ex.test_without_case (".global")) { - while (! ex.at_end ()) { + while (! NetlistSpiceReader::at_eol (ex)) { std::string n = mp_delegate->translate_net_name (read_name (ex, mp_netlist)); if (m_global_net_names.find (n) == m_global_net_names.end ()) { m_global_nets.push_back (n); @@ -766,7 +766,7 @@ SpiceCircuitDict::read_card () void SpiceCircuitDict::read_options (tl::Extractor &ex) { - while (! ex.at_end ()) { + while (! NetlistSpiceReader::at_eol (ex)) { std::string n; ex.read_word_or_quoted (n, allowed_name_chars); @@ -780,7 +780,7 @@ SpiceCircuitDict::read_options (tl::Extractor &ex) } else { // skip until end or next space ex.skip (); - while (! ex.at_end () && ! isspace (*ex)) { + while (! NetlistSpiceReader::at_eol (ex) && ! isspace (*ex)) { ++ex; } } @@ -1345,4 +1345,106 @@ void NetlistSpiceReader::read (tl::InputStream &stream, db::Netlist &netlist) } } +bool NetlistSpiceReader::at_eol (tl::Extractor &ex) +{ + // terminate at end or midline comment + return ex.at_end () || ex.test ("*") || ex.test (";"); +} + +inline static int hex_num (char c) +{ + if (c >= '0' && c <= '9') { + return (int (c - '0')); + } else if (c >= 'a' && c <= 'f') { + return (int (c - 'f') + 10); + } else { + return -1; + } +} + +std::string NetlistSpiceReader::unescape_name (const std::string &n) +{ + std::string nn; + nn.reserve (n.size ()); + + char quote = 0; + + const char *cp = n.c_str (); + while (*cp) { + + if (*cp == quote) { + + quote = 0; + ++cp; + + } else if (! quote && (*cp == '"' || *cp == '\'')) { + + quote = *cp++; + + } else if (*cp == '\\' && cp[1]) { + + if (tolower (cp[1]) == 'x') { + + cp += 2; + + char c = 0; + for (int i = 0; i < 2 && *cp; ++i) { + int n = hex_num (*cp); + if (n >= 0) { + ++cp; + c = c * 16 + char (n); + } else { + break; + } + } + + nn += c; + + } else { + ++cp; + nn += *cp++; + } + + } else { + nn += *cp++; + } + + } + + return nn; +} + +std::string NetlistSpiceReader::parse_component (tl::Extractor &ex) +{ + const char *cp = ex.skip (); + const char *cp0 = cp; + + char quote = 0; + unsigned int brackets = 0; + + while (*cp) { + if (quote) { + if (*cp == quote) { + quote = 0; + } else if (*cp == '\\' && cp[1]) { + ++cp; + } + } else if ((isspace (*cp) || *cp == '=') && ! brackets) { + break; + } else if (*cp == '"' || *cp == '\'') { + quote = *cp; + } else if (*cp == '(') { + ++brackets; + } else if (*cp == ')') { + if (brackets > 0) { + --brackets; + } + } + ++cp; + } + + ex = tl::Extractor (cp); + return std::string (cp0, cp - cp0); +} + } diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index e76efff22..b973814e2 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -29,6 +29,7 @@ #include "tlStream.h" #include "tlObject.h" #include "tlVariant.h" +#include "tlString.h" #include #include @@ -63,6 +64,29 @@ public: m_strict = s; } + /** + * @brief Returns true, if the extractor is at the end of the line + * "at_eol" is true at the line end or when a midline comment starts. + */ + static bool at_eol (tl::Extractor &ex); + + /** + * @brief Unescapes a name + * Replaces backslash sequences with the true character and removes quotes. + */ + static std::string unescape_name (const std::string &n); + + /** + * @brief Parses a netlist component (net name, expression etc.) + * Scans over the expression or net name and returns a string representing the latter. + */ + static std::string parse_component (tl::Extractor &ex); + + /** + * @brief Reads a component name + * Scans over a component name and returns the + */ + private: tl::weak_ptr mp_delegate; std::unique_ptr mp_default_delegate; diff --git a/src/db/db/dbNetlistSpiceReaderDelegate.cc b/src/db/db/dbNetlistSpiceReaderDelegate.cc index 15196cf28..8570cb3a8 100644 --- a/src/db/db/dbNetlistSpiceReaderDelegate.cc +++ b/src/db/db/dbNetlistSpiceReaderDelegate.cc @@ -32,60 +32,6 @@ namespace db // ------------------------------------------------------------------------------------------------------ -inline static int hex_num (char c) -{ - if (c >= '0' && c <= '9') { - return (int (c - '0')); - } else if (c >= 'a' && c <= 'f') { - return (int (c - 'f') + 10); - } else { - return -1; - } -} - -static std::string unescape_name (const std::string &n) -{ - std::string nn; - nn.reserve (n.size ()); - - const char *cp = n.c_str (); - while (*cp) { - - if (*cp == '\\' && cp[1]) { - - if (tolower (cp[1]) == 'x') { - - cp += 2; - - char c = 0; - for (int i = 0; i < 2 && *cp; ++i) { - int n = hex_num (*cp); - if (n >= 0) { - ++cp; - c = c * 16 + char (n); - } else { - break; - } - } - - nn += c; - - } else { - ++cp; - nn += *cp++; - } - - } else { - nn += *cp++; - } - - } - - return nn; -} - -// ------------------------------------------------------------------------------------------------------ - NetlistSpiceReaderOptions::NetlistSpiceReaderOptions () { scale = 1.0; @@ -147,7 +93,7 @@ bool NetlistSpiceReaderDelegate::wants_subcircuit (const std::string & /*circuit std::string NetlistSpiceReaderDelegate::translate_net_name (const std::string &nn) { - return unescape_name (nn); + return NetlistSpiceReader::unescape_name (nn); } void NetlistSpiceReaderDelegate::error (const std::string &msg) @@ -172,45 +118,12 @@ static db::DeviceClass *make_device_class (db::Circuit *circuit, const std::stri return cls; } -static std::string parse_component (tl::Extractor &ex) -{ - const char *cp = ex.skip (); - const char *cp0 = cp; - - char quote = 0; - unsigned int brackets = 0; - - while (*cp) { - if (quote) { - if (*cp == quote) { - quote = 0; - } else if (*cp == '\\' && cp[1]) { - ++cp; - } - } else if ((isspace (*cp) || *cp == '=') && ! brackets) { - break; - } else if (*cp == '"' || *cp == '\'') { - quote = *cp; - } else if (*cp == '(') { - ++brackets; - } else if (*cp == ')') { - if (brackets > 0) { - --brackets; - } - } - ++cp; - } - - ex = tl::Extractor (cp); - return std::string (cp0, cp - cp0); -} - void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, std::vector &strings, std::map &pv, const std::map &variables) { tl::Extractor ex (s.c_str ()); bool in_params = false; - while (! ex.at_end ()) { + while (! NetlistSpiceReader::at_eol (ex)) { if (ex.test_without_case ("params:")) { @@ -235,7 +148,7 @@ void NetlistSpiceReaderDelegate::parse_element_components (const std::string &s, ex.error (tl::to_string (tr ("Invalid syntax for parameter assignment - needs keyword followed by '='"))); } - std::string comp_name = parse_component (ex); + std::string comp_name = NetlistSpiceReader::parse_component (ex); comp_name = mp_netlist ? mp_netlist->normalize_name (comp_name) : tl::to_upper_case (comp_name); // resolve variables if string type diff --git a/src/db/unit_tests/dbNetlistReaderTests.cc b/src/db/unit_tests/dbNetlistReaderTests.cc index 47dfbee3e..db581d75e 100644 --- a/src/db/unit_tests/dbNetlistReaderTests.cc +++ b/src/db/unit_tests/dbNetlistReaderTests.cc @@ -914,6 +914,25 @@ TEST(25_dismiss_top_level) ); } +TEST(26_comments) +{ + db::Netlist nl; + + std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader26.cir"); + + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + + EXPECT_EQ (nl.to_string (), + "circuit TOP (A=A,B=B);\n" + " device NMOS '1' (S=A,G=B,D=A,B=B) (L=100,W=100,AS=0,AD=0,PS=0,PD=0);\n" + " device NMOS '2' (S='\\\\A',G='NET\"Q\"',D=A,B='NET[0]') (L=100,W=100,AS=0,AD=0,PS=0,PD=0);\n" + " device NMOS 'FET[1]' (S='\\\\A',G='NET\"Q\"',D=AAA,B='NET[0]') (L=100,W=1.7,AS=0,AD=0,PS=0,PD=0);\n" + "end;\n" + ); +} + TEST(100_ExpressionParser) { std::map vars; diff --git a/testdata/algo/nreader26.cir b/testdata/algo/nreader26.cir new file mode 100644 index 000000000..9fdd16c24 --- /dev/null +++ b/testdata/algo/nreader26.cir @@ -0,0 +1,14 @@ +* Test: dismiss empty top level circuit +; a comment + +.subckt top a b +m1 a b a b nmos * a comment +m2 a 'net"q"' \\a net[0] nmos * a comment +mfet\[1\] \a\x41a "net\"q\"" "\\a" net\[0\] nmos w=1.7u ; a comment +.ends + +* this triggered generation of a top level circuit +.param p1 17 ; a comment + +.end ; a comment +