Added tests for include feature, some bug fixes and enhancements.

This commit is contained in:
Matthias Koefferlein 2020-08-30 22:48:59 +02:00
parent a69d65daa3
commit a01eb70891
14 changed files with 309 additions and 22 deletions

View File

@ -124,7 +124,7 @@ public:
std::pair<std::string, std::string> include_expansion (const lym::Macro *macro) std::pair<std::string, std::string> include_expansion (const lym::Macro *macro)
{ {
if (m_supports_include_expansion) { if (m_supports_include_expansion) {
return MacroInterpreter::include_expansion (macro); return lym::MacroInterpreter::include_expansion (macro);
} else { } else {
return std::pair<std::string, std::string> (macro->path (), macro->text ()); return std::pair<std::string, std::string> (macro->path (), macro->text ());
} }
@ -573,27 +573,32 @@ Class<lym::Macro> decl_Macro ("lay", "Macro",
gsi::method ("real_path", &real_path, gsi::method ("real_path", &real_path,
"@brief Gets the real path for an include-encoded path and line number\n" "@brief Gets the real path for an include-encoded path and line number\n"
"\n" "\n"
"When using KLayout's include scheme based on '# %include ...', __FILE__ and __LINE__ (Ruby) or __file__ and __line__ (Python) will " "When using KLayout's include scheme based on '# %include ...', __FILE__ and __LINE__ (Ruby) will "
"not have the proper values but encoded file names. This method allows retrieving the real file by using\n" "not have the proper values but encoded file names. This method allows retrieving the real file by using\n"
"\n" "\n"
"@code\n" "@code\n"
"# Ruby\n" "# Ruby\n"
"real_file = RBA::Macro::real_path(__FILE__, __LINE__)\n" "real_file = RBA::Macro::real_path(__FILE__, __LINE__)\n"
"\n"
"# Python\n"
"real_file = pya::Macro::real_path(__file__, __line__)\n"
"@/code\n" "@/code\n"
"\n" "\n"
"This substitution is not required for top-level macros as KLayout's interpreter will automatically use this " "This substitution is not required for top-level macros as KLayout's interpreter will automatically use this "
"function instead of __FILE__ or __file__. Call this function when you need __FILE__ or __file__ from files " "function instead of __FILE__. Call this function when you need __FILE__ from files "
"included through the languages mechanisms such as 'import', 'require' or 'load' where this substitution does not happen.\n" "included through the languages mechanisms such as 'require' or 'load' where this substitution does not happen.\n"
"\n"
"For Python there is no equivalent for __LINE__, so you always have to use:\n"
"\n"
"@code\n"
"# Python"
"import inspect\n"
"real_file = pya.Macro.real_path(__file__, inspect.currentframe().f_back.f_lineno)\n"
"@/code\n"
"\n" "\n"
"This feature has been introduced in version 0.27." "This feature has been introduced in version 0.27."
) + ) +
gsi::method ("real_line", &real_line, gsi::method ("real_line", &real_line,
"@brief Gets the real line number for an include-encoded path and line number\n" "@brief Gets the real line number for an include-encoded path and line number\n"
"\n" "\n"
"When using KLayout's include scheme based on '# %include ...', __FILE__ and __LINE__ (Ruby) or __file__ and __line__ (Python) will " "When using KLayout's include scheme based on '# %include ...', __FILE__ and __LINE__ (Ruby) will "
"not have the proper values but encoded file names. This method allows retrieving the real line number by using\n" "not have the proper values but encoded file names. This method allows retrieving the real line number by using\n"
"\n" "\n"
"@code\n" "@code\n"
@ -605,8 +610,16 @@ Class<lym::Macro> decl_Macro ("lay", "Macro",
"@/code\n" "@/code\n"
"\n" "\n"
"This substitution is not required for top-level macros as KLayout's interpreter will automatically use this " "This substitution is not required for top-level macros as KLayout's interpreter will automatically use this "
"function instead of __LINE__ or __line__. Call this function when you need __LINE__ or __line__ from files " "function instead of __FILE__. Call this function when you need __FILE__ from files "
"included through the languages mechanisms such as 'import', 'require' or 'load' where this substitution does not happen.\n" "included through the languages mechanisms such as 'require' or 'load' where this substitution does not happen.\n"
"\n"
"For Python there is no equivalent for __LINE__, so you always have to use:\n"
"\n"
"@code\n"
"# Python"
"import inspect\n"
"real_line = pya.Macro.real_line(__file__, inspect.currentframe().f_back.f_lineno)\n"
"@/code\n"
"\n" "\n"
"This feature has been introduced in version 0.27." "This feature has been introduced in version 0.27."
), ),

View File

@ -71,11 +71,25 @@ MacroInterpreter::include_expansion (const lym::Macro *macro)
} }
if (ip == Macro::Ruby) { if (ip == Macro::Ruby) {
res.second = tl::replaced (res.second, "__FILE__", "RBA::Macro::real_path(__FILE__, __LINE__)");
res.second = tl::replaced (res.second, "__LINE__", "RBA::Macro::real_line(__FILE__, __LINE__)"); std::string subst;
} else if (ip == Macro::Python) { const std::string file_const ("__FILE__");
res.second = tl::replaced (res.second, "__file__", "pya.Macro.real_path(__file__, __line__)"); const std::string line_const ("__LINE__");
res.second = tl::replaced (res.second, "__line__", "pya.Macro.real_line(__file__, __line__)");
for (const char *cp = res.second.c_str (); *cp; ) {
if (strncmp (cp, file_const.c_str (), file_const.size ()) == 0 && !isalnum (cp[file_const.size ()]) && cp[file_const.size ()] != '_') {
subst += "RBA::Macro::real_path(__FILE__, __LINE__)";
cp += file_const.size ();
} else if (strncmp (cp, line_const.c_str (), line_const.size ()) == 0 && !isalnum (cp[line_const.size ()]) && cp[line_const.size ()] != '_') {
subst += "RBA::Macro::real_line(__FILE__, __LINE__)";
cp += line_const.size ();
} else {
subst += *cp++;
}
}
res.second = subst;
} }
} }

View File

@ -23,9 +23,179 @@
#include "tlUnitTest.h" #include "tlUnitTest.h"
TEST(1) #include "lymMacro.h"
#include "lymMacroInterpreter.h"
#include "gsiInterpreter.h"
#include "rba.h"
#include "pya.h"
class TestCollectorConsole
: public gsi::Console
{ {
// TODO: add tests for lym specific things public:
throw tl::CancelException (); // skip this test to indicate that there is nothing yet TestCollectorConsole () { }
~TestCollectorConsole () { }
virtual void write_str (const char *text, output_stream)
{
m_text += text;
}
virtual void flush () { }
virtual bool is_tty () { return false; }
virtual int columns () { return 80; }
virtual int rows () { return 50; }
const std::string &text () const { return m_text; }
private:
std::string m_text;
};
#if defined(HAVE_RUBY)
TEST(1_BasicRuby)
{
tl_assert (rba::RubyInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m1.rb");
macro.set_interpreter (lym::Macro::Ruby);
macro.load ();
TestCollectorConsole console;
rba::RubyInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
rba::RubyInterpreter::instance ()->remove_console (&console);
} catch (...) {
rba::RubyInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Hello, world!\n");
} }
TEST(2_RubyInclude)
{
tl_assert (rba::RubyInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m2.rb");
macro.set_interpreter (lym::Macro::Ruby);
macro.load ();
TestCollectorConsole console;
rba::RubyInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
rba::RubyInterpreter::instance ()->remove_console (&console);
} catch (...) {
rba::RubyInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Stop 1: m2.rb:2\nf: a_inc.rb:3\nStop 2: m2.rb:8\n");
}
TEST(11_DRCBasic)
{
tl_assert (rba::RubyInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m1.drc");
macro.set_interpreter (lym::Macro::DSLInterpreter);
macro.set_dsl_interpreter ("drc");
macro.load ();
TestCollectorConsole console;
rba::RubyInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
rba::RubyInterpreter::instance ()->remove_console (&console);
} catch (...) {
rba::RubyInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Result: (500,500;500,2000;1000,2000;1000,500) in m1.drc:20\n");
}
TEST(12_DRCBasic)
{
tl_assert (rba::RubyInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m2.drc");
macro.set_interpreter (lym::Macro::DSLInterpreter);
macro.set_dsl_interpreter ("drc");
macro.load ();
TestCollectorConsole console;
rba::RubyInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
rba::RubyInterpreter::instance ()->remove_console (&console);
} catch (...) {
rba::RubyInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Result: (500,500;500,2000;1000,2000;1000,500) in m2.drc:14\n");
}
#endif
#if defined(HAVE_PYTHON)
TEST(101_BasicPython)
{
tl_assert (pya::PythonInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m1.py");
macro.set_interpreter (lym::Macro::Python);
macro.load ();
TestCollectorConsole console;
pya::PythonInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
pya::PythonInterpreter::instance ()->remove_console (&console);
} catch (...) {
pya::PythonInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Hello, world!\n");
}
TEST(102_PythonInclude)
{
tl_assert (pya::PythonInterpreter::instance () != 0);
lym::Macro macro;
macro.set_file_path (tl::testsrc () + "/testdata/lym/m2.py");
macro.set_interpreter (lym::Macro::Python);
macro.load ();
TestCollectorConsole console;
pya::PythonInterpreter::instance ()->push_console (&console);
try {
EXPECT_EQ (macro.run (), 0);
pya::PythonInterpreter::instance ()->remove_console (&console);
} catch (...) {
pya::PythonInterpreter::instance ()->remove_console (&console);
throw;
}
EXPECT_EQ (console.text (), "Stop 1: m2.py:8\nf: a_inc.py:5\nStop 2: m2.py:14\n");
}
#endif

View File

@ -9,8 +9,8 @@ include($$PWD/../../lib_ut.pri)
SOURCES = \ SOURCES = \
lymBasicTests.cc lymBasicTests.cc
INCLUDEPATH += $$LYM_INC $$TL_INC $$GSI_INC INCLUDEPATH += $$RBA_INC $$PYA_INC $$LYM_INC $$TL_INC $$GSI_INC
DEPENDPATH += $$LYM_INC $$TL_INC $$GSI_INC DEPENDPATH += $$RBA_INC $$PYA_INC $$LYM_INC $$TL_INC $$GSI_INC
LIBS += -L$$DESTDIR_UT -lklayout_lym -lklayout_tl -lklayout_gsi LIBS += -L$$DESTDIR_UT -lklayout_rba -lklayout_pya -lklayout_lym -lklayout_tl -lklayout_gsi

View File

@ -78,7 +78,13 @@ IncludeExpander::read (const std::string &path, tl::InputStream &is, std::string
tl::Extractor ex (l.c_str ()); tl::Extractor ex (l.c_str ());
if (ex.test ("#") && ex.test ("%include")) { if (ex.test ("#") && ex.test ("%include")) {
std::string include_path = tl::trim (ex.skip ()); std::string include_path;
if (*ex.skip () == '"' || *ex.skip () == '\'') {
ex.read_quoted (include_path);
ex.expect_end ();
} else {
include_path = tl::trim (ex.skip ());
}
// allow some customization by employing expression interpolation // allow some customization by employing expression interpolation
include_path = tl::Eval ().interpolate (include_path); include_path = tl::Eval ().interpolate (include_path);

9
testdata/lym/a_inc.drc vendored Normal file
View File

@ -0,0 +1,9 @@
# some prep steps
ly = RBA::Layout::new
ly.create_cell("TOP")
l1 = ly.layer(1, 0)
ly.top_cell.shapes(l1).insert(RBA::Box::new(0, 0, 1000, 2000))
l2 = ly.layer(2, 0)
ly.top_cell.shapes(l2).insert(RBA::Box::new(500, 500, 1500, 2500))

6
testdata/lym/a_inc.py vendored Normal file
View File

@ -0,0 +1,6 @@
import os
def f():
return("f: " + os.path.basename(pya.Macro.real_path(__file__, lineno())) + ":" + str(pya.Macro.real_line(__file__, lineno())))

5
testdata/lym/a_inc.rb vendored Normal file
View File

@ -0,0 +1,5 @@
def f
return "f: " + File.basename(__FILE__) + ":" + __LINE__.to_s
end

22
testdata/lym/m1.drc vendored Normal file
View File

@ -0,0 +1,22 @@
# some prep steps
ly = RBA::Layout::new
ly.create_cell("TOP")
l1 = ly.layer(1, 0)
ly.top_cell.shapes(l1).insert(RBA::Box::new(0, 0, 1000, 2000))
l2 = ly.layer(2, 0)
ly.top_cell.shapes(l2).insert(RBA::Box::new(500, 500, 1500, 2500))
# actual "DRC"
source(ly.top_cell)
l1_drc = input(1, 0)
l2_drc = input(2, 0)
(l1_drc & l2_drc).output(100, 0)
l100 = ly.layer(100, 0)
puts "Result: " + RBA::Region::new(ly.top_cell.begin_shapes_rec(l100)).to_s + " in " + File.basename(__FILE__) + ":" + __LINE__.to_s

2
testdata/lym/m1.py vendored Normal file
View File

@ -0,0 +1,2 @@
print("Hello, world!")

2
testdata/lym/m1.rb vendored Normal file
View File

@ -0,0 +1,2 @@
puts "Hello, world!"

14
testdata/lym/m2.drc vendored Normal file
View File

@ -0,0 +1,14 @@
# %include "a_inc.drc"
# actual "DRC"
source(ly.top_cell)
l1_drc = input(1, 0)
l2_drc = input(2, 0)
(l1_drc & l2_drc).output(100, 0)
l100 = ly.layer(100, 0)
puts "Result: " + RBA::Region::new(ly.top_cell.begin_shapes_rec(l100)).to_s + " in " + File.basename(__FILE__) + ":" + __LINE__.to_s

15
testdata/lym/m2.py vendored Normal file
View File

@ -0,0 +1,15 @@
import os
import inspect
def lineno():
return inspect.currentframe().f_back.f_lineno
print("Stop 1: " + os.path.basename(pya.Macro.real_path(__file__, lineno())) + ":" + str(pya.Macro.real_line(__file__, lineno())))
# %include a_inc.py
print(f())
print("Stop 2: " + os.path.basename(pya.Macro.real_path(__file__, lineno())) + ":" + str(pya.Macro.real_line(__file__, lineno())))

9
testdata/lym/m2.rb vendored Normal file
View File

@ -0,0 +1,9 @@
puts "Stop 1: " + File.basename(__FILE__) + ":" + __LINE__.to_s
# %include a_inc.rb
puts f
puts "Stop 2: " + File.basename(__FILE__) + ":" + __LINE__.to_s