Auto-substitution of __FILE__/__LINE__ for include-infested files.

This commit is contained in:
Matthias Koefferlein 2020-08-30 21:34:38 +02:00
parent 740cef9619
commit a69d65daa3
4 changed files with 89 additions and 3 deletions

View File

@ -455,6 +455,24 @@ static lym::Macro *macro_by_path (const std::string &path)
return lym::MacroCollection::root ().find_macro (path); return lym::MacroCollection::root ().find_macro (path);
} }
static std::string real_path (const std::string &path, int line)
{
if (! path.empty () && path[0] == '@') {
return tl::IncludeExpander::from_string (path).translate_to_original (line).first;
} else {
return path;
}
}
static int real_line (const std::string &path, int line)
{
if (! path.empty () && path[0] == '@') {
return tl::IncludeExpander::from_string (path).translate_to_original (line).second;
} else {
return line;
}
}
Class<lym::Macro> decl_Macro ("lay", "Macro", Class<lym::Macro> decl_Macro ("lay", "Macro",
gsi::method ("path", &lym::Macro::path, gsi::method ("path", &lym::Macro::path,
"@brief Gets the path of the macro\n" "@brief Gets the path of the macro\n"
@ -551,6 +569,46 @@ Class<lym::Macro> decl_Macro ("lay", "Macro",
gsi::method ("menu_path=", &lym::Macro::set_menu_path, gsi::arg ("string"), gsi::method ("menu_path=", &lym::Macro::set_menu_path, gsi::arg ("string"),
"@brief Sets the menu path\n" "@brief Sets the menu path\n"
"See \\menu_path for details.\n" "See \\menu_path for details.\n"
) +
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 "
"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"
"\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 "
"not have the proper values but encoded file names. This method allows retrieving the real line number by using\n"
"\n"
"@code\n"
"# Ruby\n"
"real_line = RBA::Macro::real_line(__FILE__, __LINE__)\n"
"\n"
"# Python\n"
"real_line = pya::Macro::real_line(__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 __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"
"\n"
"This feature has been introduced in version 0.27."
), ),
"@brief A macro class\n" "@brief A macro class\n"
"\n" "\n"

View File

@ -1025,13 +1025,14 @@ int Macro::run () const
gsi::Interpreter *ip = script_interpreter (interpreter ()); gsi::Interpreter *ip = script_interpreter (interpreter ());
if (ip) { if (ip) {
static lym::MacroInterpreter def_interpreter;
if (! prolog ().empty ()) { if (! prolog ().empty ()) {
ip->eval_string (prolog ().c_str ()); ip->eval_string (prolog ().c_str ());
} }
std::string expanded_text; std::pair<std::string, std::string> ep = def_interpreter.include_expansion (this);
tl::IncludeExpander ie = tl::IncludeExpander::expand (path (), text (), expanded_text); ip->eval_string (ep.second.c_str (), ep.first.c_str (), 1);
ip->eval_string (expanded_text.c_str (), ie.to_string ().c_str (), 1);
if (! epilog ().empty ()) { if (! epilog ().empty ()) {
ip->eval_string (epilog ().c_str ()); ip->eval_string (epilog ().c_str ());

View File

@ -54,6 +54,32 @@ MacroInterpreter::include_expansion (const lym::Macro *macro)
{ {
std::pair<std::string, std::string> res; std::pair<std::string, std::string> res;
res.first = tl::IncludeExpander::expand (macro->path (), macro->text (), res.second).to_string (); res.first = tl::IncludeExpander::expand (macro->path (), macro->text (), res.second).to_string ();
if (res.first != macro->path ()) {
// Fix the macro's text such that include expansion does not spoil __FILE__ or __LINE__ variables
// NOTE: this will modify the column for syntax errors. Let's hope this tiny error is acceptable.
// TODO: this substitution may be somewhat naive ...
Macro::Interpreter ip = macro->interpreter ();
if (macro->interpreter () == Macro::DSLInterpreter) {
if (syntax_scheme () == "ruby") {
ip = Macro::Ruby;
} else if (syntax_scheme () == "python") {
ip = Macro::Python;
}
}
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__)");
}
}
return res; return res;
} }

View File

@ -26,6 +26,7 @@
#include "rba.h" #include "rba.h"
#include "rbaUtils.h" #include "rbaUtils.h"
#include "rbaInternal.h" #include "rbaInternal.h"
#include "tlInclude.h"
#if HAVE_RUBY_VERSION_CODE >= 20200 #if HAVE_RUBY_VERSION_CODE >= 20200
# include <ruby/debug.h> # include <ruby/debug.h>