From 79c552b300fd2e24586c7c82ac6270e854d7517b Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Fri, 2 Jul 2021 23:31:54 +0200 Subject: [PATCH] Fixed #858 (+ line continuation after blanks in Spice reader) --- src/db/db/dbNetlistSpiceReader.cc | 209 +++++++++++++++------- src/db/db/dbNetlistSpiceReader.h | 41 ++++- src/db/unit_tests/dbNetlistReaderTests.cc | 40 +++++ testdata/algo/nreader14.cir | 3 + testdata/algo/nreader14a.cir | 4 + testdata/algo/nreader14x.cir | 4 + testdata/algo/nreader15.cir | 18 ++ 7 files changed, 254 insertions(+), 65 deletions(-) create mode 100644 testdata/algo/nreader14.cir create mode 100644 testdata/algo/nreader14a.cir create mode 100644 testdata/algo/nreader14x.cir create mode 100644 testdata/algo/nreader15.cir diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index bfa207d13..0f0fe8312 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -635,8 +635,108 @@ bool NetlistSpiceReaderDelegate::element (db::Circuit *circuit, const std::strin // ------------------------------------------------------------------------------------------------------ +NetlistSpiceReader::SpiceReaderStream::SpiceReaderStream () + : mp_stream (0), m_owns_stream (false), mp_text_stream (0), m_line_number (0), m_stored_line (), m_has_stored_line (false) +{ + // .. nothing yet .. +} + +NetlistSpiceReader::SpiceReaderStream::~SpiceReaderStream () +{ + close (); +} + +void +NetlistSpiceReader::SpiceReaderStream::close () +{ + delete mp_text_stream; + mp_text_stream = 0; + + if (m_owns_stream) { + delete mp_stream; + mp_stream = 0; + m_owns_stream = false; + } +} + +std::pair +NetlistSpiceReader::SpiceReaderStream::get_line () +{ + if (mp_text_stream->at_end ()) { + return std::make_pair (std::string (), false); + } + + ++m_line_number; + + std::string l = m_has_stored_line ? m_stored_line : mp_text_stream->get_line (); + + m_has_stored_line = false; + m_stored_line.clear (); + + while (! mp_text_stream->at_end ()) { + + std::string ll = mp_text_stream->get_line (); + + tl::Extractor ex (ll.c_str ()); + if (! ex.test ("+")) { + m_stored_line = ll; + m_has_stored_line = true; + break; + } else { + ++m_line_number; + l += " "; + l += ex.get (); + } + + } + + return std::make_pair (l, true); +} + +int +NetlistSpiceReader::SpiceReaderStream::line_number () const +{ + return m_line_number; +} + +std::string +NetlistSpiceReader::SpiceReaderStream::source () const +{ + return mp_stream->source (); +} + +bool +NetlistSpiceReader::SpiceReaderStream::at_end () const +{ + return mp_text_stream->at_end (); +} + +void +NetlistSpiceReader::SpiceReaderStream::set_stream (tl::InputStream &stream) +{ + close (); + mp_stream = &stream; + mp_text_stream = new tl::TextInputStream (stream); + m_owns_stream = false; + m_has_stored_line = false; + m_line_number = 0; +} + +void +NetlistSpiceReader::SpiceReaderStream::set_stream (tl::InputStream *stream) +{ + close (); + mp_stream = stream; + mp_text_stream = new tl::TextInputStream (*stream); + m_owns_stream = true; + m_has_stored_line = false; + m_line_number = 0; +} + +// ------------------------------------------------------------------------------------------------------ + NetlistSpiceReader::NetlistSpiceReader (NetlistSpiceReaderDelegate *delegate) - : mp_netlist (0), mp_stream (), mp_delegate (delegate) + : mp_netlist (0), mp_delegate (delegate), m_stream () { static NetlistSpiceReaderDelegate std_delegate; if (! delegate) { @@ -653,7 +753,8 @@ void NetlistSpiceReader::read (tl::InputStream &stream, db::Netlist &netlist) { tl::SelfTimer timer (tl::verbosity () >= 21, tl::to_string (tr ("Reading netlist ")) + stream.source ()); - mp_stream.reset (new tl::TextInputStream (stream)); + m_stream.set_stream (stream); + mp_netlist = &netlist; mp_circuit = 0; mp_anonymous_top_circuit = 0; @@ -681,7 +782,7 @@ void NetlistSpiceReader::read (tl::InputStream &stream, db::Netlist &netlist) // NOTE: because we do a peek to capture the "+" line continuation character, we're // one line ahead. - std::string fmt_msg = tl::sprintf ("%s in %s, line %d", ex.msg (), mp_stream->source (), mp_stream->line_number () - 1); + std::string fmt_msg = tl::sprintf ("%s in %s, line %d", ex.msg (), m_stream.source (), m_stream.line_number ()); finish (); throw tl::Exception (fmt_msg); @@ -737,11 +838,9 @@ void NetlistSpiceReader::build_global_nets () void NetlistSpiceReader::finish () { - while (! m_streams.empty ()) { - pop_stream (); - } + m_streams.clear (); + m_stream.close (); - mp_stream.reset (0); mp_netlist = 0; mp_circuit = 0; mp_nets_by_name.reset (0); @@ -749,7 +848,7 @@ void NetlistSpiceReader::finish () void NetlistSpiceReader::push_stream (const std::string &path) { - tl::URI current_uri (mp_stream->source ()); + tl::URI current_uri (m_stream.source ()); tl::URI new_uri (path); tl::InputStream *istream; @@ -757,80 +856,68 @@ void NetlistSpiceReader::push_stream (const std::string &path) if (tl::is_absolute (path)) { istream = new tl::InputStream (path); } else { - istream = new tl::InputStream (tl::combine_path (tl::dirname (mp_stream->source ()), path)); + istream = new tl::InputStream (tl::combine_path (tl::dirname (m_stream.source ()), path)); } } else { istream = new tl::InputStream (current_uri.resolved (new_uri).to_abstract_path ()); } - m_streams.push_back (std::make_pair (istream, mp_stream.release ())); - mp_stream.reset (new tl::TextInputStream (*istream)); + m_streams.push_back (SpiceReaderStream ()); + m_streams.back ().swap (m_stream); + m_stream.set_stream (istream); } void NetlistSpiceReader::pop_stream () { if (! m_streams.empty ()) { - - mp_stream.reset (m_streams.back ().second); - delete m_streams.back ().first; - + m_stream.swap (m_streams.back ()); m_streams.pop_back (); - } } bool NetlistSpiceReader::at_end () { - return mp_stream->at_end () && m_streams.empty (); + return m_stream.at_end () && m_streams.empty (); } std::string NetlistSpiceReader::get_line () { - if (! m_stored_line.empty ()) { - std::string l; - l.swap (m_stored_line); - return l; + std::pair lp; + + while (true) { + + lp = m_stream.get_line (); + if (! lp.second) { + + if (m_streams.empty ()) { + break; + } else { + pop_stream (); + } + + } else { + + tl::Extractor ex (lp.first.c_str ()); + if (ex.test_without_case (".include") || ex.test_without_case (".inc")) { + + std::string path; + ex.read_word_or_quoted (path, allowed_name_chars); + + push_stream (path); + + } else if (ex.at_end () || ex.test ("*")) { + + // skip empty and comment lines + + } else { + break; + } + + } + } - std::string l; - - do { - - while (mp_stream->at_end ()) { - if (m_streams.empty ()) { - return std::string (); - } - pop_stream (); - } - - l = mp_stream->get_line (); - while (! mp_stream->at_end () && mp_stream->peek_char () == '+') { - mp_stream->get_char (); - l += mp_stream->get_line (); - } - - tl::Extractor ex (l.c_str ()); - if (ex.test_without_case (".include") || ex.test_without_case (".inc")) { - - std::string path; - ex.read_word_or_quoted (path, allowed_name_chars); - - push_stream (path); - - l.clear (); - - } else if (ex.at_end () || ex.test ("*")) { - l.clear (); - } - - } while (l.empty ()); - - return l; -} - -void NetlistSpiceReader::unget_line (const std::string &l) -{ - m_stored_line = l; + return lp.first; } bool NetlistSpiceReader::subcircuit_captured (const std::string &nc_name) @@ -928,7 +1015,7 @@ void NetlistSpiceReader::error (const std::string &msg) void NetlistSpiceReader::warn (const std::string &msg) { - std::string fmt_msg = tl::sprintf ("%s in %s, line %d", msg, mp_stream->source (), mp_stream->line_number () - 1); + std::string fmt_msg = tl::sprintf ("%s in %s, line %d", msg, m_stream.source (), m_stream.line_number ()); tl::warn << fmt_msg; } diff --git a/src/db/db/dbNetlistSpiceReader.h b/src/db/db/dbNetlistSpiceReader.h index 24dd6f111..01491e6c9 100644 --- a/src/db/db/dbNetlistSpiceReader.h +++ b/src/db/db/dbNetlistSpiceReader.h @@ -168,14 +168,48 @@ public: virtual void read (tl::InputStream &stream, db::Netlist &netlist); private: + + class SpiceReaderStream + { + public: + SpiceReaderStream (); + ~SpiceReaderStream (); + + void set_stream (tl::InputStream &stream); + void set_stream (tl::InputStream *stream); + void close (); + + std::pair get_line(); + int line_number () const; + std::string source () const; + bool at_end () const; + + void swap (SpiceReaderStream &other) + { + std::swap (mp_stream, other.mp_stream); + std::swap (m_owns_stream, other.m_owns_stream); + std::swap (mp_text_stream, other.mp_text_stream); + std::swap (m_line_number, other.m_line_number); + std::swap (m_stored_line, other.m_stored_line); + std::swap (m_has_stored_line, other.m_has_stored_line); + } + + private: + tl::InputStream *mp_stream; + bool m_owns_stream; + tl::TextInputStream *mp_text_stream; + int m_line_number; + std::string m_stored_line; + bool m_has_stored_line; + }; + db::Netlist *mp_netlist; db::Circuit *mp_circuit; db::Circuit *mp_anonymous_top_circuit; - std::unique_ptr mp_stream; tl::weak_ptr mp_delegate; - std::vector > m_streams; + std::list m_streams; + SpiceReaderStream m_stream; std::unique_ptr > mp_nets_by_name; - std::string m_stored_line; std::map m_captured; std::vector m_global_nets; std::set m_global_net_names; @@ -191,7 +225,6 @@ private: bool read_card (); std::string read_name (tl::Extractor &ex); std::string get_line (); - void unget_line (const std::string &l); void error (const std::string &msg); void warn (const std::string &msg); void finish (); diff --git a/src/db/unit_tests/dbNetlistReaderTests.cc b/src/db/unit_tests/dbNetlistReaderTests.cc index 1dd39a5c1..987c432af 100644 --- a/src/db/unit_tests/dbNetlistReaderTests.cc +++ b/src/db/unit_tests/dbNetlistReaderTests.cc @@ -550,3 +550,43 @@ TEST(13_NoGlobalNetsIfNotUsed) ); } +TEST(14_IncludeWithError) +{ + db::Netlist nl; + + std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader14.cir"); + + try { + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + EXPECT_EQ (true, false); // must not happen + } catch (tl::Exception &ex) { + EXPECT_EQ (ex.msg (), "'M' element must have four nodes in " + std::string (tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader14x.cir")) + ", line 3"); + } +} + +TEST(15_ContinuationWithBlanks) +{ + db::Netlist nl; + + std::string path = tl::combine_path (tl::combine_path (tl::testdata (), "algo"), "nreader15.cir"); + + db::NetlistSpiceReader reader; + tl::InputStream is (path); + reader.read (is, nl); + + EXPECT_EQ (nl.to_string (), + "circuit SUBCKT ($1=$1,'A[5]<1>'='A[5]<1>','V42(%)'='V42(%)',Z=Z,GND=GND,GND$1=GND$1);\n" + " subcircuit HVPMOS D_$1 ($1='V42(%)',$2=$3,$3=Z,$4=$1);\n" + " subcircuit HVPMOS D_$2 ($1='V42(%)',$2='A[5]<1>',$3=$3,$4=$1);\n" + " subcircuit HVNMOS D_$3 ($1=GND,$2=$3,$3=GND,$4=GND$1);\n" + " subcircuit HVNMOS D_$4 ($1=GND,$2=$3,$3=Z,$4=GND$1);\n" + " subcircuit HVNMOS D_$5 ($1=GND,$2='A[5]<1>',$3=$3,$4=GND$1);\n" + "end;\n" + "circuit HVPMOS ($1=(null),$2=(null),$3=(null),$4=(null));\n" + "end;\n" + "circuit HVNMOS ($1=(null),$2=(null),$3=(null),$4=(null));\n" + "end;\n" + ); +} diff --git a/testdata/algo/nreader14.cir b/testdata/algo/nreader14.cir new file mode 100644 index 000000000..20a308b46 --- /dev/null +++ b/testdata/algo/nreader14.cir @@ -0,0 +1,3 @@ + +.include "nreader14a.cir" + diff --git a/testdata/algo/nreader14a.cir b/testdata/algo/nreader14a.cir new file mode 100644 index 000000000..08414862b --- /dev/null +++ b/testdata/algo/nreader14a.cir @@ -0,0 +1,4 @@ +.subckt INVX1 1 2 3 4 5 6 + .include nreader14x.cir +.ends + diff --git a/testdata/algo/nreader14x.cir b/testdata/algo/nreader14x.cir new file mode 100644 index 000000000..74e02ef7f --- /dev/null +++ b/testdata/algo/nreader14x.cir @@ -0,0 +1,4 @@ +m$1 1 5 2 4 mlvpmos w=1.5um l=0.25um +m$2 3 5 2 6 mlvnmos w=0.95um l=0.25um +m1 1 *an error + diff --git a/testdata/algo/nreader15.cir b/testdata/algo/nreader15.cir new file mode 100644 index 000000000..3ff574f5e --- /dev/null +++ b/testdata/algo/nreader15.cir @@ -0,0 +1,18 @@ +.SUBCKT SUBCKT + + \$1 A[5]<1> V42\x28\x25\x29 Z gnd gnd$1 +* device instance $1 r0 *1 0,0 HVPMOS +XD_$1 V42\x28\x25\x29 \$3 Z \$1 + + HVPMOS PARAMS: L=0.2 W=1 AS=0.18 AD=0.18 + + PS=2.16 PD=2.16 +XD_$2 + + V42\x28\x25\x29 A[5]<1> \$3 \$1 + + HVPMOS PARAMS: L=0.2 W=1 AS=0.18 AD=0.18 + + PS=2.16 PD=2.16 +XD_$3 gnd \$3 gnd gnd$1 HVNMOS PARAMS: L=1.13 W=2.12 PS=6 PD=6 AS=0 AD=0 + + + +XD_$4 gnd \$3 Z gnd$1 HVNMOS PARAMS: L=0.4 W=0.4 PS=1.16 PD=1.16 AS=0.19 AD=0.19 +XD_$5 gnd A[5]<1> \$3 gnd$1 HVNMOS + + PARAMS: L=0.4 W=0.4 PS=1.76 PD=1.76 AS=0.19 AD=0.19 + +.ENDS SUBCKT