mirror of https://github.com/KLayout/klayout.git
Proposal for fixing issue #1784 (Spice throws error at midline semicolon comments)
This commit is contained in:
parent
d3921844d6
commit
7978a2bb1d
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
Loading…
Reference in New Issue