mirror of https://github.com/KLayout/klayout.git
Refactoring of the interpreter scheme with the goal to provide a functional cleanup. This is required if someone stops inside the debugger. Without a proper cleanup the application becomes dysfunctional.
This commit is contained in:
parent
f2b1661647
commit
a8f3df7aa5
|
|
@ -1859,6 +1859,7 @@ CODE
|
|||
@in_context = func
|
||||
return yield(*args)
|
||||
rescue => ex
|
||||
RBA::MacroExecutionContext::ignore_next_exception
|
||||
raise("'" + func + "': " + ex.to_s)
|
||||
ensure
|
||||
@in_context = in_context_outer
|
||||
|
|
@ -1873,6 +1874,7 @@ CODE
|
|||
@in_context = func
|
||||
return yield(*args)
|
||||
rescue => ex
|
||||
RBA::MacroExecutionContext::ignore_next_exception
|
||||
raise("'" + func + "': " + ex.to_s)
|
||||
ensure
|
||||
@in_context = nil
|
||||
|
|
@ -2097,7 +2099,7 @@ CODE
|
|||
end
|
||||
end
|
||||
|
||||
def _start(macro_path)
|
||||
def _start(job_description)
|
||||
|
||||
# clearing the selection avoids some nasty problems
|
||||
view = RBA::LayoutView::current
|
||||
|
|
@ -2106,8 +2108,7 @@ CODE
|
|||
@total_timer = RBA::Timer::new
|
||||
@total_timer.start
|
||||
|
||||
@drc_progress = RBA::AbstractProgress::new("DRC: " + macro_path)
|
||||
|
||||
@drc_progress = RBA::AbstractProgress::new(job_description)
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -2249,6 +2250,7 @@ CODE
|
|||
@netter_data = nil
|
||||
|
||||
if final
|
||||
|
||||
@total_timer.stop
|
||||
if @verbose
|
||||
mem = RBA::Timer::memory_size
|
||||
|
|
@ -2258,22 +2260,33 @@ CODE
|
|||
info("Total elapsed: #{'%.3f'%(@total_timer.sys+@total_timer.user)}s")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if final && @log_file
|
||||
@log_file.close
|
||||
@log_file = nil
|
||||
_cleanup
|
||||
|
||||
end
|
||||
|
||||
# force garbage collection
|
||||
GC.start
|
||||
|
||||
# unlocks the UI
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def _cleanup
|
||||
|
||||
if @log_file
|
||||
@log_file.close
|
||||
@log_file = nil
|
||||
end
|
||||
|
||||
# unlocks the UI
|
||||
if @drc_progress
|
||||
@drc_progress._destroy
|
||||
@drc_progress = nil
|
||||
|
||||
end
|
||||
|
||||
GC.start
|
||||
|
||||
end
|
||||
|
||||
def _take_data
|
||||
|
|
|
|||
|
|
@ -17,33 +17,50 @@
|
|||
<text>
|
||||
module DRC
|
||||
|
||||
def DRC.execute_drc(macro, generator, rdb_index = nil)
|
||||
class DRCExecutable < RBA::Executable
|
||||
|
||||
drc = DRCEngine::new
|
||||
drc._rdb_index = rdb_index
|
||||
drc._generator = generator
|
||||
drc._start(macro.path)
|
||||
def initialize(macro, generator, rdb_index = nil)
|
||||
|
||||
begin
|
||||
@drc = DRCEngine::new
|
||||
@drc._rdb_index = rdb_index
|
||||
@drc._generator = generator
|
||||
|
||||
@macro = macro
|
||||
|
||||
end
|
||||
|
||||
def execute
|
||||
|
||||
@drc._start("DRC: " + @macro.path)
|
||||
|
||||
# Set a debugger scope so that our errors end up with the debugger set to the DRC's line
|
||||
RBA::MacroExecutionContext::set_debugger_scope(macro.path)
|
||||
# No verbosity set in drc engine - we cannot use the engine's logger
|
||||
RBA::Logger::verbosity >= 10 && RBA::Logger::info("Running #{macro.path}")
|
||||
drc.instance_eval(macro.text, macro.path)
|
||||
RBA::MacroExecutionContext::set_debugger_scope(@macro.path)
|
||||
|
||||
begin
|
||||
|
||||
# No verbosity set in drc engine - we cannot use the engine's logger
|
||||
RBA::Logger::verbosity >= 10 && RBA::Logger::info("Running #{@macro.path}")
|
||||
@drc.instance_eval(@macro.text, @macro.path)
|
||||
|
||||
rescue => ex
|
||||
|
||||
@drc.error("In #{@macro.path}: #{ex.to_s}")
|
||||
RBA::MacroExecutionContext::ignore_next_exception
|
||||
raise ex
|
||||
|
||||
end
|
||||
|
||||
nil
|
||||
|
||||
end
|
||||
|
||||
def cleanup
|
||||
|
||||
# Remove the debugger scope
|
||||
RBA::MacroExecutionContext::remove_debugger_scope
|
||||
|
||||
rescue => ex
|
||||
|
||||
drc.error("In #{macro.path}: #{ex.to_s}")
|
||||
RBA::MacroExecutionContext::ignore_next_exception
|
||||
raise ex
|
||||
|
||||
ensure
|
||||
|
||||
# cleans up and creates layout and report views
|
||||
drc._finish
|
||||
@drc._finish
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -70,11 +87,17 @@ module DRC
|
|||
# create a template for the macro editor:
|
||||
create_template(":/drc-templates/drc.lym")
|
||||
|
||||
# if available, create a menu branch
|
||||
if RBA::Application::instance && RBA::Application::instance.main_window
|
||||
mw = RBA::Application::instance.main_window
|
||||
mw.menu.insert_menu("tools_menu.verification_group+", "drc", "DRC")
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Implements the execute method
|
||||
def execute(macro)
|
||||
DRC::execute_drc(macro, @recipe.generator("script" => macro.path))
|
||||
def executable(macro)
|
||||
DRCExecutable::new(macro, @recipe.generator("script" => macro.path))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -100,20 +123,20 @@ module DRC
|
|||
end
|
||||
|
||||
# Implements the execute method
|
||||
def execute(macro)
|
||||
DRC::execute_drc(macro, @recipe.generator("script" => macro.path))
|
||||
def executable(macro)
|
||||
DRCExecutable::new(macro, @recipe.generator("script" => macro.path))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# A recipe implementation allowing the LVS run to be redone
|
||||
# A recipe implementation allowing the DRC run to be redone
|
||||
class DRCRecipe < RBA::Recipe
|
||||
|
||||
def initialize
|
||||
super("drc", "DRC recipe")
|
||||
end
|
||||
|
||||
def execute(params)
|
||||
def executable(params)
|
||||
|
||||
script = params["script"]
|
||||
if ! script
|
||||
|
|
@ -123,7 +146,7 @@ module DRC
|
|||
macro = RBA::Macro::macro_by_path(script)
|
||||
macro || raise("Can't find DRC script #{script} - unable to re-run")
|
||||
|
||||
DRC::execute_drc(macro, self.generator("script" => script), params["rdb_index"])
|
||||
DRCExecutable::new(macro, self.generator("script" => script), params["rdb_index"])
|
||||
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -629,6 +629,72 @@ Class<tl::GlobPattern> decl_GlobPattern ("tl", "GlobPattern",
|
|||
"This class has been introduced in version 0.26."
|
||||
);
|
||||
|
||||
class Executable_Impl
|
||||
: public tl::Executable, public gsi::ObjectBase
|
||||
{
|
||||
public:
|
||||
Executable_Impl ()
|
||||
: tl::Executable ()
|
||||
{
|
||||
// makes the object owned by the C++ side (registrar). This way we don't need to keep a
|
||||
// singleton instance.
|
||||
keep ();
|
||||
}
|
||||
|
||||
virtual tl::Variant execute ()
|
||||
{
|
||||
if (execute_cb.can_issue ()) {
|
||||
return execute_cb.issue<tl::Executable, tl::Variant> (&tl::Executable::execute);
|
||||
} else {
|
||||
return tl::Variant ();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void cleanup ()
|
||||
{
|
||||
if (cleanup_cb.can_issue ()) {
|
||||
cleanup_cb.issue<tl::Executable> (&tl::Executable::cleanup);
|
||||
}
|
||||
}
|
||||
|
||||
gsi::Callback execute_cb;
|
||||
gsi::Callback cleanup_cb;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace tl
|
||||
{
|
||||
template <> struct type_traits<gsi::Executable_Impl> : public type_traits<tl::Executable> { };
|
||||
}
|
||||
|
||||
namespace gsi
|
||||
{
|
||||
|
||||
Class<tl::Executable> decl_Executable ("tl", "ExecutableBase",
|
||||
gsi::Methods (),
|
||||
"@hide"
|
||||
);
|
||||
|
||||
Class<Executable_Impl> decl_Executable_Impl (decl_Executable, "tl", "Executable",
|
||||
gsi::callback ("execute", &Executable_Impl::execute, &Executable_Impl::execute_cb,
|
||||
"@brief Reimplement this method to provide the functionality of the executable.\n"
|
||||
"This method is supposed to execute the operation with the given parameters and return the desired output."
|
||||
) +
|
||||
gsi::callback ("cleanup", &Executable_Impl::cleanup, &Executable_Impl::cleanup_cb,
|
||||
"@brief Reimplement this method to provide post-mortem cleanup functionality.\n"
|
||||
"This method is always called after execute terminated."
|
||||
),
|
||||
"@brief A generic executable object\n"
|
||||
"This object is a delegate for implementing the actual function of some generic executable function. "
|
||||
"In addition to the plain execution, if offers a post-mortem cleanup callback which is always executed, even "
|
||||
"if execute's implementation is cancelled in the debugger.\n"
|
||||
"\n"
|
||||
"Parameters are kept as a generic key/value map.\n"
|
||||
"\n"
|
||||
"This class has been introduced in version 0.27."
|
||||
);
|
||||
|
||||
class Recipe_Impl
|
||||
: public tl::Recipe, public gsi::ObjectBase
|
||||
{
|
||||
|
|
@ -641,16 +707,16 @@ public:
|
|||
keep ();
|
||||
}
|
||||
|
||||
virtual tl::Variant execute (const std::map<std::string, tl::Variant> ¶ms) const
|
||||
virtual tl::Executable *executable (const std::map<std::string, tl::Variant> ¶ms) const
|
||||
{
|
||||
if (execute_cb.can_issue ()) {
|
||||
return execute_cb.issue<tl::Recipe, tl::Variant, const std::map<std::string, tl::Variant> &> (&tl::Recipe::execute, params);
|
||||
if (executable_cb.can_issue ()) {
|
||||
return executable_cb.issue<tl::Recipe, tl::Executable *, const std::map<std::string, tl::Variant> &> (&tl::Recipe::executable, params);
|
||||
} else {
|
||||
return tl::Variant ();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
gsi::Callback execute_cb;
|
||||
gsi::Callback executable_cb;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -688,10 +754,13 @@ Class<Recipe_Impl> decl_Recipe_Impl ("tl", "Recipe",
|
|||
"@brief Delivers the generator string from the given parameters.\n"
|
||||
"The generator string can be used with \\make to re-run the recipe."
|
||||
) +
|
||||
gsi::callback ("execute", &Recipe_Impl::execute, &Recipe_Impl::execute_cb, gsi::arg ("params"),
|
||||
"@brief Reimplement this method to provide the functionality of the recipe.\n"
|
||||
"This method is supposed to re-run the recipe with the given parameters and deliver the "
|
||||
"the intended output object."
|
||||
gsi::callback ("executable", &Recipe_Impl::executable, &Recipe_Impl::executable_cb, gsi::arg ("params"),
|
||||
"@brief Reimplement this method to provide an executable object for the actual implementation.\n"
|
||||
"The reasoning behind this architecture is to supply a cleanup callback. This is useful when the "
|
||||
"actual function is executed as a script and the script terminates in the debugger. The cleanup callback "
|
||||
"allows implementing any kind of post-mortem action despite being cancelled in the debugger.\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27 and replaces 'execute'."
|
||||
),
|
||||
"@brief A facility for providing reproducable recipes\n"
|
||||
"The idea of this facility is to provide a service by which an object\n"
|
||||
|
|
|
|||
|
|
@ -3003,6 +3003,11 @@ MacroEditorDialog::translate_pseudo_id (size_t &file_id, int &line)
|
|||
}
|
||||
}
|
||||
|
||||
static void exit_from_macro ()
|
||||
{
|
||||
throw tl::ExitException ();
|
||||
}
|
||||
|
||||
void
|
||||
MacroEditorDialog::exception_thrown (gsi::Interpreter *interpreter, size_t file_id, int line, const std::string &eclass, const std::string &emsg, const gsi::StackTraceProvider *stack_trace_provider)
|
||||
{
|
||||
|
|
@ -3012,7 +3017,7 @@ MacroEditorDialog::exception_thrown (gsi::Interpreter *interpreter, size_t file_
|
|||
}
|
||||
|
||||
if (!m_in_exec) {
|
||||
throw tl::ExitException ();
|
||||
exit_from_macro ();
|
||||
}
|
||||
|
||||
// avoid recursive breakpoints and exception catches from the console while in a breakpoint or exception stop
|
||||
|
|
@ -3098,7 +3103,7 @@ MacroEditorDialog::exception_thrown (gsi::Interpreter *interpreter, size_t file_
|
|||
}
|
||||
|
||||
if (! m_in_exec) {
|
||||
throw tl::ExitException ();
|
||||
exit_from_macro ();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3106,7 +3111,7 @@ void
|
|||
MacroEditorDialog::trace (gsi::Interpreter *interpreter, size_t file_id, int line, const gsi::StackTraceProvider *stack_trace_provider)
|
||||
{
|
||||
if (!m_in_exec) {
|
||||
throw tl::ExitException ();
|
||||
exit_from_macro ();
|
||||
}
|
||||
|
||||
// avoid recursive breakpoints and exception catches from the console while in a breakpoint or exception stop
|
||||
|
|
@ -3157,7 +3162,7 @@ MacroEditorDialog::trace (gsi::Interpreter *interpreter, size_t file_id, int lin
|
|||
}
|
||||
|
||||
if (! m_in_exec) {
|
||||
throw tl::ExitException ();
|
||||
exit_from_macro ();
|
||||
}
|
||||
|
||||
} else if (++m_trace_count == 20) {
|
||||
|
|
@ -3175,7 +3180,7 @@ MacroEditorDialog::trace (gsi::Interpreter *interpreter, size_t file_id, int lin
|
|||
m_process_events_interval = std::max (0.05, std::min (2.0, (m_last_process_events - start).seconds () * 5.0));
|
||||
|
||||
if (!m_in_exec) {
|
||||
throw tl::ExitException ();
|
||||
exit_from_macro ();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3590,6 +3595,9 @@ MacroEditorDialog::run (int stop_stack_depth, lym::Macro *macro)
|
|||
} catch (tl::ExitException &) {
|
||||
m_stop_stack_depth = -1;
|
||||
// .. ignore exit exceptions ..
|
||||
} catch (tl::BreakException &) {
|
||||
m_stop_stack_depth = -1;
|
||||
// .. ignore break exceptions ..
|
||||
} catch (tl::ScriptError &re) {
|
||||
m_stop_stack_depth = -1;
|
||||
handle_error (re);
|
||||
|
|
|
|||
|
|
@ -17,44 +17,52 @@
|
|||
<text>
|
||||
module LVS
|
||||
|
||||
def self.execute_lvs(macro, generator, l2ndb_index = nil)
|
||||
class LVSExecutable < RBA::Executable
|
||||
|
||||
timer = RBA::Timer::new
|
||||
timer.start
|
||||
lvs = LVSEngine::new
|
||||
lvs._l2ndb_index = l2ndb_index
|
||||
lvs._generator = generator
|
||||
def initialize(macro, generator, l2ndb_index = nil)
|
||||
|
||||
lvs_progress = RBA::AbstractProgress::new("LVS: " + macro.path)
|
||||
@lvs = LVSEngine::new
|
||||
@lvs._l2ndb_index = l2ndb_index
|
||||
@lvs._generator = generator
|
||||
|
||||
begin
|
||||
|
||||
# Set a debugger scope so that our errors end up with the debugger set to the LVS's line
|
||||
RBA::MacroExecutionContext::set_debugger_scope(macro.path)
|
||||
# No verbosity set in lvs engine - we cannot use the engine's logger
|
||||
RBA::Logger::verbosity >= 10 && RBA::Logger::info("Running #{macro.path}")
|
||||
lvs.instance_eval(macro.text, macro.path)
|
||||
# Remove the debugger scope
|
||||
RBA::MacroExecutionContext::remove_debugger_scope
|
||||
|
||||
rescue => ex
|
||||
|
||||
lvs.error("In #{macro.path}: #{ex.to_s}")
|
||||
RBA::MacroExecutionContext::ignore_next_exception
|
||||
raise ex
|
||||
|
||||
ensure
|
||||
|
||||
# cleans up and creates layout and report views
|
||||
lvs._finish
|
||||
|
||||
# unlocks the UI
|
||||
lvs_progress._destroy
|
||||
@macro = macro
|
||||
|
||||
end
|
||||
|
||||
timer.stop
|
||||
lvs.info("Total run time: #{'%.3f'%(timer.sys+timer.user)}s")
|
||||
def execute
|
||||
|
||||
@lvs._start("LVS: " + @macro.path)
|
||||
|
||||
# Set a debugger scope so that our errors end up with the debugger set to the LVS's line
|
||||
RBA::MacroExecutionContext::set_debugger_scope(@macro.path)
|
||||
|
||||
begin
|
||||
|
||||
# No verbosity set in lvs engine - we cannot use the engine's logger
|
||||
RBA::Logger::verbosity >= 10 && RBA::Logger::info("Running #{@macro.path}")
|
||||
@lvs.instance_eval(@macro.text, @macro.path)
|
||||
|
||||
rescue => ex
|
||||
|
||||
@lvs.error("In #{@macro.path}: #{ex.to_s}")
|
||||
RBA::MacroExecutionContext::ignore_next_exception
|
||||
raise ex
|
||||
|
||||
end
|
||||
|
||||
nil
|
||||
|
||||
end
|
||||
|
||||
def cleanup
|
||||
|
||||
# Remove the debugger scope
|
||||
RBA::MacroExecutionContext::remove_debugger_scope
|
||||
|
||||
# cleans up and creates layout and report views
|
||||
@lvs._finish
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -88,8 +96,8 @@ module LVS
|
|||
end
|
||||
|
||||
# Implements the execute method
|
||||
def execute(macro)
|
||||
LVS::execute_lvs(macro, @recipe.generator("script" => macro.path))
|
||||
def executable(macro)
|
||||
LVSExecutable::new(macro, @recipe.generator("script" => macro.path))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -115,8 +123,8 @@ module LVS
|
|||
end
|
||||
|
||||
# Implements the execute method
|
||||
def execute(macro)
|
||||
LVS::execute_lvs(macro, @recipe.generator("script" => macro.path))
|
||||
def executable(macro)
|
||||
LVSExecutable::new(macro, @recipe.generator("script" => macro.path))
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -128,7 +136,7 @@ module LVS
|
|||
super("lvs", "LVS recipe")
|
||||
end
|
||||
|
||||
def execute(params)
|
||||
def executable(params)
|
||||
|
||||
script = params["script"]
|
||||
if ! script
|
||||
|
|
@ -138,7 +146,7 @@ module LVS
|
|||
macro = RBA::Macro::macro_by_path(script)
|
||||
macro || raise("Can't find LVS script #{script} - unable to re-run")
|
||||
|
||||
LVS::execute_lvs(macro, self.generator("script" => script), params["l2ndb_index"])
|
||||
LVSExecutable::new(macro, self.generator("script" => script), params["l2ndb_index"])
|
||||
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -142,10 +142,12 @@ public:
|
|||
m_name = name;
|
||||
}
|
||||
|
||||
virtual void execute (const lym::Macro *macro) const
|
||||
virtual tl::Executable *executable (const lym::Macro *macro) const
|
||||
{
|
||||
if (f_execute.can_issue ()) {
|
||||
f_execute.issue<MacroInterpreter, const lym::Macro *> (&MacroInterpreter::execute, macro);
|
||||
if (f_executable.can_issue ()) {
|
||||
return f_executable.issue<MacroInterpreter, tl::Executable *, const lym::Macro *> (&MacroInterpreter::executable, macro);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -240,7 +242,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
gsi::Callback f_execute;
|
||||
gsi::Callback f_executable;
|
||||
|
||||
private:
|
||||
tl::RegisteredClass <lym::MacroInterpreter> *mp_registration;
|
||||
|
|
@ -378,13 +380,15 @@ Class<gsi::MacroInterpreter> decl_MacroInterpreter ("lay", "MacroInterpreter",
|
|||
"Before version 0.25 this attribute was a reimplementable method. It has been turned into an attribute for "
|
||||
"performance reasons in version 0.25.\n"
|
||||
) +
|
||||
gsi::callback ("execute", &gsi::MacroInterpreter::execute, &gsi::MacroInterpreter::f_execute, gsi::arg ("macro"),
|
||||
"@brief Gets called to execute a macro\n"
|
||||
"This method must be reimplemented to execute the macro. "
|
||||
"The system will call this script when a macro with interpreter type 'dsl' and the "
|
||||
"name of this interpreter is run."
|
||||
gsi::callback ("executable", &gsi::MacroInterpreter::executable, &gsi::MacroInterpreter::f_executable, gsi::arg ("macro"),
|
||||
"@brief Returns the executable object which implements the macro execution\n"
|
||||
"This method must be reimplemented to return an \\Executable object for the actual implementation. "
|
||||
"The system will use this function to execute the script when a macro with interpreter type 'dsl' and the "
|
||||
"name of this interpreter is run.\n"
|
||||
"\n"
|
||||
"@param macro The macro to execute\n"
|
||||
"\n"
|
||||
"This method has been introduced in version 0.27 and replaces the 'execute' method.\n"
|
||||
),
|
||||
"@brief A custom interpreter for a DSL (domain specific language)\n"
|
||||
"\n"
|
||||
|
|
@ -413,6 +417,21 @@ Class<gsi::MacroInterpreter> decl_MacroInterpreter ("lay", "MacroInterpreter",
|
|||
"just evaluates the script text:\n"
|
||||
"\n"
|
||||
"@code\n"
|
||||
"class SimpleExecutable < RBA::Excutable\n"
|
||||
"\n"
|
||||
" # Constructor\n"
|
||||
" def initialize(macro)\n"
|
||||
" @macro = macro\n"
|
||||
" end\n"
|
||||
" \n"
|
||||
" # Implements the execute method\n"
|
||||
" def execute\n"
|
||||
" eval(@macro.text, nil, @macro.path)"
|
||||
" nil\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
"end\n"
|
||||
"\n"
|
||||
"class SimpleInterpreter < RBA::MacroInterpreter\n"
|
||||
"\n"
|
||||
" # Constructor\n"
|
||||
|
|
@ -428,8 +447,8 @@ Class<gsi::MacroInterpreter> decl_MacroInterpreter ("lay", "MacroInterpreter",
|
|||
" end\n"
|
||||
" \n"
|
||||
" # Implements the execute method\n"
|
||||
" def execute(macro)\n"
|
||||
" eval(macro.text, nil, macro.path)\n"
|
||||
" def executable(macro)\n"
|
||||
" SimpleExecutable::new(macro)\n"
|
||||
" end\n"
|
||||
"\n"
|
||||
"end\n"
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@
|
|||
namespace lym
|
||||
{
|
||||
|
||||
void
|
||||
MacroInterpreter::execute (const lym::Macro *) const
|
||||
tl::Executable *
|
||||
MacroInterpreter::executable (const lym::Macro *) const
|
||||
{
|
||||
throw tl::Exception (tl::to_string (QObject::tr ("execute() implementation missing for DSL interpreter")));
|
||||
throw tl::Exception (tl::to_string (QObject::tr ("executable() implementation missing for DSL interpreter")));
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -107,7 +107,10 @@ MacroInterpreter::execute_macro (const lym::Macro *macro)
|
|||
std::pair<std::string, std::string> et = cls->include_expansion (macro);
|
||||
if (et.first.empty () || et.first == macro->path ()) {
|
||||
|
||||
cls->execute (macro);
|
||||
std::unique_ptr<tl::Executable> eo (cls->executable (macro));
|
||||
if (eo.get ()) {
|
||||
eo->do_execute ();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
|
|
@ -116,7 +119,10 @@ MacroInterpreter::execute_macro (const lym::Macro *macro)
|
|||
tmp_macro.assign (*macro);
|
||||
tmp_macro.set_text (et.second);
|
||||
tmp_macro.set_file_path (et.first);
|
||||
cls->execute (&tmp_macro);
|
||||
std::unique_ptr<tl::Executable> eo (cls->executable (&tmp_macro));
|
||||
if (eo.get ()) {
|
||||
eo->do_execute ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#include "lymCommon.h"
|
||||
|
||||
#include "tlRecipe.h"
|
||||
#include "gsiObject.h"
|
||||
#include "tlClassRegistry.h"
|
||||
|
||||
|
|
@ -63,11 +64,11 @@ public:
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Executes the macro
|
||||
* @brief Creates the executable for a macro
|
||||
*
|
||||
* This method must be reimplemented to provide the actual execution of the macro.
|
||||
* The caller will delete the returned object.
|
||||
*/
|
||||
virtual void execute (const lym::Macro *macro) const;
|
||||
virtual tl::Executable *executable (const lym::Macro *macro) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the storage scheme
|
||||
|
|
|
|||
|
|
@ -22,10 +22,73 @@
|
|||
|
||||
#include "tlRecipe.h"
|
||||
#include "tlString.h"
|
||||
#include "tlLog.h"
|
||||
#include "tlException.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace tl
|
||||
{
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Executable implementation
|
||||
|
||||
Executable::Executable ()
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
Executable::~Executable ()
|
||||
{
|
||||
// .. nothing yet ..
|
||||
}
|
||||
|
||||
tl::Variant
|
||||
Executable::do_execute ()
|
||||
{
|
||||
tl::Variant res;
|
||||
|
||||
try {
|
||||
res = execute ();
|
||||
do_cleanup ();
|
||||
} catch (...) {
|
||||
do_cleanup ();
|
||||
throw;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
Executable::do_cleanup ()
|
||||
{
|
||||
try {
|
||||
cleanup ();
|
||||
} catch (tl::Exception &ex) {
|
||||
tl::error << ex.msg ();
|
||||
} catch (std::runtime_error &ex) {
|
||||
tl::error << ex.what ();
|
||||
} catch (...) {
|
||||
// ignore exceptions here
|
||||
}
|
||||
}
|
||||
|
||||
tl::Variant
|
||||
Executable::execute ()
|
||||
{
|
||||
// .. the default implementation does nothing ..
|
||||
return tl::Variant ();
|
||||
}
|
||||
|
||||
void
|
||||
Executable::cleanup ()
|
||||
{
|
||||
// .. the default implementation does nothing ..
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// Recipe implementation
|
||||
|
||||
Recipe::Recipe (const std::string &name, const std::string &description)
|
||||
: tl::RegisteredClass<tl::Recipe> (this, 0, name.c_str (), false)
|
||||
{
|
||||
|
|
@ -83,9 +146,14 @@ tl::Variant Recipe::make (const std::string &generator, const std::map<std::stri
|
|||
|
||||
if (! recipe_obj) {
|
||||
return tl::Variant ();
|
||||
} else {
|
||||
return recipe_obj->execute (params);
|
||||
}
|
||||
|
||||
std::unique_ptr<Executable> eo (recipe_obj->executable (params));
|
||||
if (! eo.get ()) {
|
||||
return tl::Variant ();
|
||||
}
|
||||
|
||||
return eo->do_execute ();
|
||||
}
|
||||
|
||||
} // namespace tl
|
||||
|
|
|
|||
|
|
@ -32,6 +32,80 @@
|
|||
namespace tl
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A base class for an executable item
|
||||
*
|
||||
* This is a little more than just a function: it also provides a post-mortem
|
||||
* action (cleanup). This is useful for script-based applications: as the
|
||||
* main thread can be terminated in the debugger through ExitException unconditionally,
|
||||
* cleanup() provides a way to implement actions after such an event.
|
||||
*/
|
||||
|
||||
class TL_PUBLIC Executable
|
||||
{
|
||||
public:
|
||||
Executable ();
|
||||
virtual ~Executable ();
|
||||
|
||||
/**
|
||||
* @brief Runs the function with error handling and cleanup
|
||||
*/
|
||||
tl::Variant do_execute ();
|
||||
|
||||
/**
|
||||
* @brief Runs the specific job
|
||||
*/
|
||||
virtual tl::Variant execute ();
|
||||
|
||||
/**
|
||||
* @brief Called after the job terminated
|
||||
*/
|
||||
virtual void cleanup ();
|
||||
|
||||
private:
|
||||
void do_cleanup ();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Provides a convenience class for an executable with parameters
|
||||
*/
|
||||
class TL_PUBLIC ExecutableWithParameters
|
||||
: public Executable
|
||||
{
|
||||
public:
|
||||
ExecutableWithParameters (const std::map<std::string, tl::Variant> ¶ms)
|
||||
: m_params (params)
|
||||
{ }
|
||||
|
||||
/**
|
||||
* @brief An utility function to get a parameter
|
||||
*/
|
||||
template <class T>
|
||||
static T get_value (const std::map<std::string, tl::Variant> ¶ms, const std::string &pname, const T &def_value)
|
||||
{
|
||||
std::map<std::string, tl::Variant>::const_iterator p = params.find (pname);
|
||||
if (p != params.end ()) {
|
||||
const tl::Variant &v = p->second;
|
||||
return v.to<T> ();
|
||||
} else {
|
||||
return def_value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the parameters
|
||||
*/
|
||||
const std::map<std::string, tl::Variant> ¶meters () const
|
||||
{
|
||||
return m_params;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, tl::Variant> m_params;
|
||||
|
||||
void do_cleanup ();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A facility for providing reproducable recipes
|
||||
*
|
||||
|
|
@ -80,21 +154,6 @@ public:
|
|||
return m_description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief An utility function to get a parameters
|
||||
*/
|
||||
template <class T>
|
||||
static T get_value (const std::map<std::string, tl::Variant> ¶ms, const std::string &pname, const T &def_value)
|
||||
{
|
||||
std::map<std::string, tl::Variant>::const_iterator p = params.find (pname);
|
||||
if (p != params.end ()) {
|
||||
const tl::Variant &v = p->second;
|
||||
return v.to<T> ();
|
||||
} else {
|
||||
return def_value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serializes the given recipe
|
||||
*/
|
||||
|
|
@ -110,9 +169,11 @@ public:
|
|||
static tl::Variant make (const std::string &generator, const std::map<std::string, tl::Variant> ¶ms = std::map<std::string, tl::Variant> ());
|
||||
|
||||
/**
|
||||
* @brief Recipe interface: executes the recipe with the given parameters
|
||||
* @brief Returns the executable object which actually implements the action to take
|
||||
*
|
||||
* The returned object is deleted by the caller.
|
||||
*/
|
||||
virtual tl::Variant execute (const std::map<std::string, tl::Variant> ¶ms) const = 0;
|
||||
virtual Executable *executable (const std::map<std::string, tl::Variant> ¶ms) const = 0;
|
||||
|
||||
private:
|
||||
Recipe (const Recipe &) : tl::RegisteredClass<tl::Recipe> (this) { }
|
||||
|
|
|
|||
|
|
@ -27,18 +27,40 @@
|
|||
|
||||
namespace {
|
||||
|
||||
class MyExecutable : public tl::ExecutableWithParameters
|
||||
{
|
||||
public:
|
||||
static bool cleanup_called;
|
||||
|
||||
MyExecutable (const std::map<std::string, tl::Variant> ¶ms)
|
||||
: tl::ExecutableWithParameters (params)
|
||||
{ }
|
||||
|
||||
tl::Variant execute ()
|
||||
{
|
||||
int a = get_value (parameters (), "A", 0);
|
||||
double b = get_value (parameters (), "B", 0.0);
|
||||
double c = get_value (parameters (), "C", 1.0);
|
||||
bool crash = get_value (parameters (), "X", false);
|
||||
if (crash) {
|
||||
throw tl::Exception ("crashed");
|
||||
}
|
||||
return tl::Variant (b * a * c);
|
||||
}
|
||||
|
||||
void cleanup ()
|
||||
{
|
||||
cleanup_called = true;
|
||||
}
|
||||
};
|
||||
|
||||
bool MyExecutable::cleanup_called = false;
|
||||
|
||||
class MyRecipe : public tl::Recipe
|
||||
{
|
||||
public:
|
||||
MyRecipe () : tl::Recipe ("test_recipe", "description") { }
|
||||
|
||||
tl::Variant execute (const std::map<std::string, tl::Variant> ¶ms) const
|
||||
{
|
||||
int a = get_value (params, "A", 0);
|
||||
double b = get_value (params, "B", 0.0);
|
||||
double c = get_value (params, "C", 1.0);
|
||||
return tl::Variant (b * a * c);
|
||||
}
|
||||
tl::Executable *executable (const std::map<std::string, tl::Variant> ¶ms) const { return new MyExecutable (params); }
|
||||
};
|
||||
|
||||
static MyRecipe my_recipe;
|
||||
|
|
@ -54,11 +76,24 @@ TEST(1)
|
|||
std::string g = my_recipe.generator (params);
|
||||
EXPECT_EQ (g, "test_recipe: A=#7,B=##6");
|
||||
|
||||
MyExecutable::cleanup_called = false;
|
||||
tl::Variant res = tl::Recipe::make (g);
|
||||
EXPECT_EQ (res.to_double (), 42.0);
|
||||
EXPECT_EQ (MyExecutable::cleanup_called, true);
|
||||
|
||||
std::map<std::string, tl::Variant> padd;
|
||||
padd["C"] = tl::Variant(1.5);
|
||||
res = tl::Recipe::make (g, padd);
|
||||
EXPECT_EQ (res.to_double (), 63.0);
|
||||
|
||||
MyExecutable::cleanup_called = false;
|
||||
padd.clear ();
|
||||
padd["X"] = tl::Variant(true);
|
||||
try {
|
||||
res = tl::Recipe::make (g, padd);
|
||||
EXPECT_EQ (1, 0);
|
||||
} catch (tl::Exception &ex) {
|
||||
EXPECT_EQ (ex.msg (), "crashed");
|
||||
}
|
||||
EXPECT_EQ (MyExecutable::cleanup_called, true);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue