From a01eb7089173eefe11707f65e986702823c84651 Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Sun, 30 Aug 2020 22:48:59 +0200 Subject: [PATCH] Added tests for include feature, some bug fixes and enhancements. --- src/lym/lym/gsiDeclLymMacro.cc | 33 ++++-- src/lym/lym/lymMacroInterpreter.cc | 24 +++- src/lym/unit_tests/lymBasicTests.cc | 176 +++++++++++++++++++++++++++- src/lym/unit_tests/unit_tests.pro | 6 +- src/tl/tl/tlInclude.cc | 8 +- testdata/lym/a_inc.drc | 9 ++ testdata/lym/a_inc.py | 6 + testdata/lym/a_inc.rb | 5 + testdata/lym/m1.drc | 22 ++++ testdata/lym/m1.py | 2 + testdata/lym/m1.rb | 2 + testdata/lym/m2.drc | 14 +++ testdata/lym/m2.py | 15 +++ testdata/lym/m2.rb | 9 ++ 14 files changed, 309 insertions(+), 22 deletions(-) create mode 100644 testdata/lym/a_inc.drc create mode 100644 testdata/lym/a_inc.py create mode 100644 testdata/lym/a_inc.rb create mode 100644 testdata/lym/m1.drc create mode 100644 testdata/lym/m1.py create mode 100644 testdata/lym/m1.rb create mode 100644 testdata/lym/m2.drc create mode 100644 testdata/lym/m2.py create mode 100644 testdata/lym/m2.rb diff --git a/src/lym/lym/gsiDeclLymMacro.cc b/src/lym/lym/gsiDeclLymMacro.cc index 617a337a7..d3e2028d9 100644 --- a/src/lym/lym/gsiDeclLymMacro.cc +++ b/src/lym/lym/gsiDeclLymMacro.cc @@ -124,7 +124,7 @@ public: std::pair include_expansion (const lym::Macro *macro) { if (m_supports_include_expansion) { - return MacroInterpreter::include_expansion (macro); + return lym::MacroInterpreter::include_expansion (macro); } else { return std::pair (macro->path (), macro->text ()); } @@ -573,27 +573,32 @@ Class decl_Macro ("lay", "Macro", gsi::method ("real_path", &real_path, "@brief Gets the real path for an include-encoded path and line number\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" "\n" "@code\n" "# Ruby\n" "real_file = RBA::Macro::real_path(__FILE__, __LINE__)\n" - "\n" - "# Python\n" - "real_file = pya::Macro::real_path(__file__, __line__)\n" "@/code\n" "\n" "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 " - "included through the languages mechanisms such as 'import', 'require' or 'load' where this substitution does not happen.\n" + "function instead of __FILE__. Call this function when you need __FILE__ from files " + "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" "This feature has been introduced in version 0.27." ) + gsi::method ("real_line", &real_line, "@brief Gets the real line number for an include-encoded path and line number\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" "\n" "@code\n" @@ -605,8 +610,16 @@ Class decl_Macro ("lay", "Macro", "@/code\n" "\n" "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 " - "included through the languages mechanisms such as 'import', 'require' or 'load' where this substitution does not happen.\n" + "function instead of __FILE__. Call this function when you need __FILE__ from files " + "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" "This feature has been introduced in version 0.27." ), diff --git a/src/lym/lym/lymMacroInterpreter.cc b/src/lym/lym/lymMacroInterpreter.cc index 995eaa0a1..90d41e863 100644 --- a/src/lym/lym/lymMacroInterpreter.cc +++ b/src/lym/lym/lymMacroInterpreter.cc @@ -71,11 +71,25 @@ MacroInterpreter::include_expansion (const lym::Macro *macro) } 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__)"); - } else if (ip == Macro::Python) { - res.second = tl::replaced (res.second, "__file__", "pya.Macro.real_path(__file__, __line__)"); - res.second = tl::replaced (res.second, "__line__", "pya.Macro.real_line(__file__, __line__)"); + + std::string subst; + const std::string file_const ("__FILE__"); + const std::string line_const ("__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; + } } diff --git a/src/lym/unit_tests/lymBasicTests.cc b/src/lym/unit_tests/lymBasicTests.cc index e027ccbd5..c8914718d 100644 --- a/src/lym/unit_tests/lymBasicTests.cc +++ b/src/lym/unit_tests/lymBasicTests.cc @@ -23,9 +23,179 @@ #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 - throw tl::CancelException (); // skip this test to indicate that there is nothing yet +public: + 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 diff --git a/src/lym/unit_tests/unit_tests.pro b/src/lym/unit_tests/unit_tests.pro index 2bc16943b..8aec7683c 100644 --- a/src/lym/unit_tests/unit_tests.pro +++ b/src/lym/unit_tests/unit_tests.pro @@ -9,8 +9,8 @@ include($$PWD/../../lib_ut.pri) SOURCES = \ lymBasicTests.cc -INCLUDEPATH += $$LYM_INC $$TL_INC $$GSI_INC -DEPENDPATH += $$LYM_INC $$TL_INC $$GSI_INC +INCLUDEPATH += $$RBA_INC $$PYA_INC $$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 diff --git a/src/tl/tl/tlInclude.cc b/src/tl/tl/tlInclude.cc index 4ed1b7508..76a067521 100644 --- a/src/tl/tl/tlInclude.cc +++ b/src/tl/tl/tlInclude.cc @@ -78,7 +78,13 @@ IncludeExpander::read (const std::string &path, tl::InputStream &is, std::string tl::Extractor ex (l.c_str ()); 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 include_path = tl::Eval ().interpolate (include_path); diff --git a/testdata/lym/a_inc.drc b/testdata/lym/a_inc.drc new file mode 100644 index 000000000..7d1ed315f --- /dev/null +++ b/testdata/lym/a_inc.drc @@ -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)) + diff --git a/testdata/lym/a_inc.py b/testdata/lym/a_inc.py new file mode 100644 index 000000000..fe6b5645c --- /dev/null +++ b/testdata/lym/a_inc.py @@ -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()))) + diff --git a/testdata/lym/a_inc.rb b/testdata/lym/a_inc.rb new file mode 100644 index 000000000..6973a7793 --- /dev/null +++ b/testdata/lym/a_inc.rb @@ -0,0 +1,5 @@ + +def f + return "f: " + File.basename(__FILE__) + ":" + __LINE__.to_s +end + diff --git a/testdata/lym/m1.drc b/testdata/lym/m1.drc new file mode 100644 index 000000000..ee51e7fdf --- /dev/null +++ b/testdata/lym/m1.drc @@ -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 + + diff --git a/testdata/lym/m1.py b/testdata/lym/m1.py new file mode 100644 index 000000000..5d09ce2d5 --- /dev/null +++ b/testdata/lym/m1.py @@ -0,0 +1,2 @@ + +print("Hello, world!") diff --git a/testdata/lym/m1.rb b/testdata/lym/m1.rb new file mode 100644 index 000000000..53588161e --- /dev/null +++ b/testdata/lym/m1.rb @@ -0,0 +1,2 @@ + +puts "Hello, world!" diff --git a/testdata/lym/m2.drc b/testdata/lym/m2.drc new file mode 100644 index 000000000..eec335e80 --- /dev/null +++ b/testdata/lym/m2.drc @@ -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 diff --git a/testdata/lym/m2.py b/testdata/lym/m2.py new file mode 100644 index 000000000..401d57365 --- /dev/null +++ b/testdata/lym/m2.py @@ -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()))) + diff --git a/testdata/lym/m2.rb b/testdata/lym/m2.rb new file mode 100644 index 000000000..e4c9031fd --- /dev/null +++ b/testdata/lym/m2.rb @@ -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 +