Ruby: better error reporting (full backtrace)

Originally committed to SVN as r933.
This commit is contained in:
pomyk 2007-02-08 13:17:29 +00:00
parent 538a825eff
commit 99cc856076
3 changed files with 108 additions and 100 deletions

View file

@ -68,6 +68,8 @@ namespace Automation4 {
RubyScript * RubyScript::inst = NULL; // current Ruby Script RubyScript * RubyScript::inst = NULL; // current Ruby Script
RubyProgressSink* RubyProgressSink::inst = NULL; RubyProgressSink* RubyProgressSink::inst = NULL;
VALUE RubyScript::RubyAegisub = Qfalse; VALUE RubyScript::RubyAegisub = Qfalse;
wxString RubyScript::backtrace = _T("");
wxString RubyScript::error = _T("");
// RubyScript // RubyScript
@ -104,6 +106,8 @@ namespace Automation4 {
//ruby_options(1, opt); //ruby_options(1, opt);
ruby_init_loadpath(); ruby_init_loadpath();
RubyScript::inst = this; RubyScript::inst = this;
error = _T("");
backtrace = _T("");
if(!RubyAegisub) { if(!RubyAegisub) {
RubyAegisub = rb_define_module("Aegisub"); RubyAegisub = rb_define_module("Aegisub");
rb_define_module_function(RubyAegisub, "register_macro",reinterpret_cast<RB_HOOK>(&RubyFeatureMacro::RubyRegister), 4); rb_define_module_function(RubyAegisub, "register_macro",reinterpret_cast<RB_HOOK>(&RubyFeatureMacro::RubyRegister), 4);
@ -111,6 +115,7 @@ namespace Automation4 {
rb_define_module_function(RubyAegisub, "text_extents",reinterpret_cast<RB_HOOK>(&RubyTextExtents), 2); rb_define_module_function(RubyAegisub, "text_extents",reinterpret_cast<RB_HOOK>(&RubyTextExtents), 2);
rb_define_module_function(RubyAegisub, "frame_to_time",reinterpret_cast<RB_HOOK>(&RubyFrameToTime), 1); rb_define_module_function(RubyAegisub, "frame_to_time",reinterpret_cast<RB_HOOK>(&RubyFrameToTime), 1);
rb_define_module_function(RubyAegisub, "time_to_frame",reinterpret_cast<RB_HOOK>(&RubyTimeToFrame), 1); rb_define_module_function(RubyAegisub, "time_to_frame",reinterpret_cast<RB_HOOK>(&RubyTimeToFrame), 1);
rb_define_module_function(rb_eException, "set_backtrace",reinterpret_cast<RB_HOOK>(&backtrace_hook), 1);
rb_define_module_function(RubyScript::RubyAegisub, "progress_set",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetProgress), 1); rb_define_module_function(RubyScript::RubyAegisub, "progress_set",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetProgress), 1);
rb_define_module_function(RubyScript::RubyAegisub, "progress_task",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetTask), 1); rb_define_module_function(RubyScript::RubyAegisub, "progress_task",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetTask), 1);
rb_define_module_function(RubyScript::RubyAegisub, "progress_title",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetTitle), 1); rb_define_module_function(RubyScript::RubyAegisub, "progress_title",reinterpret_cast<RB_HOOK>(&RubyProgressSink::RubySetTitle), 1);
@ -131,10 +136,8 @@ namespace Automation4 {
rb_protect(rbLoadWrapper, rb_str_new2(t), &status); rb_protect(rbLoadWrapper, rb_str_new2(t), &status);
if(status > 0) // something bad happened (probably parsing error) if(status > 0) // something bad happened (probably parsing error)
{ {
VALUE err = rb_errinfo(); wxString *err = new wxString(_T("An error occurred initialising the script file \"") + GetFilename() + _T("\":\n\n") + GetError());
if(TYPE(err) == T_STRING) throw err->c_str();
throw StringValueCStr(err);
else throw "Error loading script";
} }
VALUE global_var = rb_gv_get("$script_name"); VALUE global_var = rb_gv_get("$script_name");
@ -151,18 +154,17 @@ namespace Automation4 {
version = wxString(StringValueCStr(global_var), wxConvUTF8); version = wxString(StringValueCStr(global_var), wxConvUTF8);
loaded = true; loaded = true;
} }
catch (const char* e) { catch (const wchar_t* e) {
Destroy(); Destroy();
loaded = false; loaded = false;
wxString *err = new wxString(e, wxConvUTF8); throw;
throw err->c_str();
} }
} }
void RubyScript::Destroy() void RubyScript::Destroy()
{ {
if(loaded) { if(loaded) {
// ruby_finalize(); // broken in 1.9 ?_? ruby_finalize(); // broken in 1.9 ?_?
} }
// remove features // remove features
@ -230,6 +232,17 @@ namespace Automation4 {
return Qnil; return Qnil;
} }
wxString RubyScript::GetError()
{
return wxString(error + _T("\n") + backtrace);
}
void RubyScript::RubyError()
{
wxMessageBox(RubyScript::inst->GetError(), _T("Error"),wxICON_ERROR | wxOK);
}
// RubyFeature // RubyFeature
RubyFeature::RubyFeature(ScriptFeatureClass _featureclass, const wxString &_name) RubyFeature::RubyFeature(ScriptFeatureClass _featureclass, const wxString &_name)
@ -249,7 +262,6 @@ namespace Automation4 {
{ {
VALUE res = rb_ary_new2(ints.size()); VALUE res = rb_ary_new2(ints.size());
// create an array-style table with an integer vector in it // create an array-style table with an integer vector in it
// leave the new table on top of the stack
for (int i = 0; i != ints.size(); ++i) { for (int i = 0; i != ints.size(); ++i) {
int k = ints[i]; int k = ints[i];
rb_ary_push(res, rb_int2inum(k)); rb_ary_push(res, rb_int2inum(k));
@ -259,10 +271,9 @@ namespace Automation4 {
void RubyFeature::ThrowError() void RubyFeature::ThrowError()
{ {
/* wxString err(lua_tostring(L, -1), wxConvUTF8); // wxString err(_T("Error running script") + RubyScript::inst->GetError());
lua_pop(L, 1); // wxLogError(err);
wxLogError(err); }
*/ }
// RubyFeatureMacro // RubyFeatureMacro
@ -291,26 +302,22 @@ namespace Automation4 {
if (no_validate) if (no_validate)
return true; return true;
try { RubyProgressSink::inst = NULL;
RubyProgressSink::inst = NULL; RubyAssFile *subsobj = new RubyAssFile(subs, true, true);
RubyAssFile *subsobj = new RubyAssFile(subs, true, true); VALUE *argv = ALLOCA_N(VALUE, 3);
VALUE *argv = ALLOCA_N(VALUE, 3); argv[0] = subsobj->rbAssFile;
argv[0] = subsobj->rbAssFile; argv[1] = CreateIntegerArray(selected); // selected items;
argv[1] = CreateIntegerArray(selected); // selected items; argv[2] = INT2FIX(active);
argv[2] = INT2FIX(active); RubyCallArguments arg(rb_mKernel, rb_to_id(validation_fun), 3, argv);
RubyCallArguments arg(rb_mKernel, rb_to_id(validation_fun), 3, argv); VALUE result;
VALUE result; RubyThreadedCall call(&arg, &result);
RubyThreadedCall call(&arg, &result); wxThread::ExitCode code = call.Wait();
wxThread::ExitCode code = call.Wait(); if(code)
if(code) {
{ wxMessageBox(RubyScript::GetError(), _T("Error running validation function"),wxICON_ERROR | wxOK);
return false;
} } else if(result != Qnil && result != Qfalse) {
if(result != Qnil && result != Qfalse) return true;
return true;
}catch (const char* e) {
wxString *err = new wxString(e, wxConvUTF8);
wxMessageBox(*err, _T("Error running validation function"),wxICON_ERROR | wxOK);
} }
return false; return false;
@ -318,66 +325,55 @@ namespace Automation4 {
void RubyFeatureMacro::Process(AssFile *subs, const std::vector<int> &selected, int active, wxWindow * const progress_parent) void RubyFeatureMacro::Process(AssFile *subs, const std::vector<int> &selected, int active, wxWindow * const progress_parent)
{ {
try { rb_gc_disable();
rb_gc_disable(); delete RubyProgressSink::inst;
delete RubyProgressSink::inst; RubyProgressSink::inst = new RubyProgressSink(progress_parent, false);
RubyProgressSink::inst = new RubyProgressSink(progress_parent, false); RubyProgressSink::inst->SetTitle(GetName());
RubyProgressSink::inst->SetTitle(GetName());
// do call // do call
RubyAssFile *subsobj = new RubyAssFile(subs, true, true); RubyAssFile *subsobj = new RubyAssFile(subs, true, true);
VALUE *argv = ALLOCA_N(VALUE, 3); VALUE *argv = ALLOCA_N(VALUE, 3);
argv[0] = subsobj->rbAssFile; argv[0] = subsobj->rbAssFile;
argv[1] = CreateIntegerArray(selected); // selected items; argv[1] = CreateIntegerArray(selected); // selected items;
argv[2] = INT2FIX(active); argv[2] = INT2FIX(active);
RubyCallArguments arg(rb_mKernel, rb_to_id(macro_fun), 3, argv); RubyCallArguments arg(rb_mKernel, rb_to_id(macro_fun), 3, argv);
VALUE result; VALUE result;
RubyThreadedCall call(&arg, &result); RubyThreadedCall call(&arg, &result);
RubyProgressSink::inst->ShowModal(); RubyProgressSink::inst->ShowModal();
wxThread::ExitCode code = call.Wait(); wxThread::ExitCode code = call.Wait();
delete RubyProgressSink::inst; delete RubyProgressSink::inst;
RubyProgressSink::inst = NULL; RubyProgressSink::inst = NULL;
/*if(code) // error reporting doesn't work in ruby 1.9 if(TYPE(result) == T_ARRAY)
{
bool end = false;
for(int i = 0; i < RARRAY(result)->len && !end; ++i)
{ {
if(TYPE(result) == T_STRING) VALUE p = RARRAY(result)->ptr[i]; // some magic in code below to allow variable output
throw StringValueCStr(result); if(TYPE(p) != T_ARRAY) {
else throw "Error running macro"; p = result;
} end = true;
else*/ if(TYPE(result) == T_ARRAY)
{
bool end = false;
for(int i = 0; i < RARRAY(result)->len && !end; ++i)
{
VALUE p = RARRAY(result)->ptr[i]; // some magic in code below to allow variable output
if(TYPE(p) != T_ARRAY) {
p = result;
end = true;
}
switch(TYPE(RARRAY(p)->ptr[0])) {
case T_HASH: // array of hashes = subs
subsobj->RubyUpdateAssFile(p);
break;
case T_FIXNUM: // array of ints = selection
int num = RARRAY(p)->len;
std::vector<int> sel(num);
for(int i = 0; i < num; ++i) {
sel[i] = FIX2INT(RARRAY(p)->ptr[i]);
}
FrameMain *frame = AegisubApp::Get()->frame;
frame->SubsBox->LoadFromAss(AssFile::top, true, true);
frame->SubsBox->SetSelectionFromAbsolute(sel);
break;
}
} }
switch(TYPE(RARRAY(p)->ptr[0])) {
case T_HASH: // array of hashes = subs
subsobj->RubyUpdateAssFile(p);
break;
case T_FIXNUM: // array of ints = selection
int num = RARRAY(p)->len;
std::vector<int> sel(num);
for(int i = 0; i < num; ++i) {
sel[i] = FIX2INT(RARRAY(p)->ptr[i]);
}
FrameMain *frame = AegisubApp::Get()->frame;
frame->SubsBox->LoadFromAss(AssFile::top, true, true);
frame->SubsBox->SetSelectionFromAbsolute(sel);
break;
}
} }
delete subsobj;
} catch (const char* e) {
wxString *err = new wxString(e, wxConvUTF8);
wxMessageBox(*err, _T("Error running macro"),wxICON_ERROR | wxOK);
} }
delete subsobj;
rb_gc_enable(); rb_gc_enable();
rb_gc_start(); rb_gc_start();
} }
@ -407,7 +403,7 @@ namespace Automation4 {
wxWakeUpIdle(); wxWakeUpIdle();
} }
if(error) { if(error) {
*result = rb_errinfo(); RubyScript::RubyError();
return (wxThread::ExitCode)1; return (wxThread::ExitCode)1;
} }
return (wxThread::ExitCode)0; return (wxThread::ExitCode)0;
@ -653,5 +649,18 @@ namespace Automation4 {
VALUE rbGcWrapper(VALUE arg){rb_gc_start(); return Qtrue;} VALUE rbGcWrapper(VALUE arg){rb_gc_start(); return Qtrue;}
VALUE rbAss2RbWrapper(VALUE arg){return RubyAssFile::AssEntryToRuby(reinterpret_cast<AssEntry*>(arg));} VALUE rbAss2RbWrapper(VALUE arg){return RubyAssFile::AssEntryToRuby(reinterpret_cast<AssEntry*>(arg));}
VALUE rb2AssWrapper(VALUE arg){return reinterpret_cast<VALUE>(RubyAssFile::RubyToAssEntry(arg));} VALUE rb2AssWrapper(VALUE arg){return reinterpret_cast<VALUE>(RubyAssFile::RubyToAssEntry(arg));}
VALUE RubyScript::backtrace_hook(VALUE self, VALUE backtr)
{
int len = RARRAY_LEN(backtr);
VALUE err = rb_funcall(self, rb_intern("to_s"), 0);
error = wxString(StringValueCStr(err), wxConvUTF8);
for(int i = 0; i < len; ++i)
{
VALUE str = RARRAY_PTR(backtr)[i];
wxString line(StringValueCStr(str), wxConvUTF8);
backtrace.Append(line + _T("\n"));
}
return backtr;
}
}; };

View file

@ -155,24 +155,29 @@ namespace Automation4 {
// Class of Ruby scripts // Class of Ruby scripts
class RubyScript : public Script { class RubyScript : public Script {
friend class RubyFeature; friend class RubyFeature;
friend class RubyProgressSink;
private: private:
static wxString error;
static wxString backtrace;
void Create(); // load script and create internal structures etc. void Create(); // load script and create internal structures etc.
void Destroy(); // destroy internal structures, unreg features and delete environment void Destroy(); // destroy internal structures, unreg features and delete environment
static RubyScript* GetScriptObject(); static RubyScript* GetScriptObject();
static RubyScript* inst;
static VALUE RubyTextExtents(VALUE self, VALUE style, VALUE text); static VALUE RubyTextExtents(VALUE self, VALUE style, VALUE text);
static VALUE RubyFrameToTime(VALUE self, VALUE frame); static VALUE RubyFrameToTime(VALUE self, VALUE frame);
static VALUE RubyTimeToFrame(VALUE self, VALUE time); static VALUE RubyTimeToFrame(VALUE self, VALUE time);
static int RubyInclude(); static VALUE backtrace_hook(VALUE self, VALUE backtr);
public: public:
RubyScript(const wxString &filename); RubyScript(const wxString &filename);
static void RubyError();
static wxString GetError();
virtual ~RubyScript(); virtual ~RubyScript();
virtual void Reload(); virtual void Reload();
static VALUE RubyAegisub; static VALUE RubyAegisub;
static RubyScript* inst;
}; };
@ -257,7 +262,7 @@ namespace Automation4 {
VALUE rbError(VALUE arg); VALUE rbError(VALUE arg);
typedef VALUE (*RB_HOOK)(...); typedef VALUE (*RB_HOOK)(...);
typedef VALUE (*RB_HOOK2)(VALUE); typedef VALUE (*RB_HOOK2)(VALUE);
#define STR2SYM(x) ID2SYM(rb_intern(x)) #define STR2SYM(x) ID2SYM(rb_intern(x))
}; };

View file

@ -25,9 +25,6 @@ def k_replace_macro(subs, sel, act)
(opt[:style] =="" || l[:style] == opt[:style]) # and has the right style (opt[:style] =="" || l[:style] == opt[:style]) # and has the right style
end end
return subs return subs
rescue Exception
debug_out($!.inspect << "\n" << caller.join("\n"))
return nil
end end
def k_replace_filter(subs, opt) def k_replace_filter(subs, opt)
@ -36,9 +33,6 @@ def k_replace_filter(subs, opt)
opt[:style] =="" || l[:style] == opt[:style] # and has the right style opt[:style] =="" || l[:style] == opt[:style] # and has the right style
end end
return subs return subs
rescue Exception
debug_out($!.inspect << "\n" << caller.join("\n"))
return nil
end end
def k_replace_cfg(subs, store) def k_replace_cfg(subs, store)