Merge pull request #1808 from KLayout/bugfix/issue-1784

Proposal for fixing issue #1784 (Spice throws error at midline semico…
This commit is contained in:
Matthias Köfferlein 2024-07-30 18:58:28 +02:00 committed by GitHub
commit ba899b391c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 169 additions and 97 deletions

View File

@ -54,7 +54,7 @@ read_param_card (tl::Extractor &ex, const db::Netlist *netlist, std::map<std::st
// taken from:
// https://nmg.gitlab.io/ngspice-manual/circuitdescription/paramparametricnetlists/paramline.html
while (! ex.at_end ()) {
while (! NetlistSpiceReader::at_eol (ex)) {
std::string name;
ex.read_word (name);
@ -333,7 +333,7 @@ read_name (tl::Extractor &ex, const db::Netlist *netlist)
{
std::string n;
ex.read_word_or_quoted (n, allowed_name_chars);
return netlist->normalize_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);
}
}

View File

@ -29,6 +29,7 @@
#include "tlStream.h"
#include "tlObject.h"
#include "tlVariant.h"
#include "tlString.h"
#include <map>
#include <string>
@ -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<NetlistSpiceReaderDelegate> mp_delegate;
std::unique_ptr<NetlistSpiceReaderDelegate> mp_default_delegate;

View File

@ -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<std::string> &strings, std::map<std::string, tl::Variant> &pv, const std::map<std::string, tl::Variant> &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

View File

@ -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<std::string, tl::Variant> vars;

14
testdata/algo/nreader26.cir vendored Normal file
View File

@ -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