| 1 |
/* |
| 2 |
* MDRubyCore.m |
| 3 |
* Alchemusica |
| 4 |
* |
| 5 |
* Created by Toshi Nagata on 08/03/19. |
| 6 |
* Copyright 2008-2017 Toshi Nagata. All rights reserved. |
| 7 |
* |
| 8 |
This program is free software; you can redistribute it and/or modify |
| 9 |
it under the terms of the GNU General Public License as published by |
| 10 |
the Free Software Foundation version 2 of the License. |
| 11 |
|
| 12 |
This program is distributed in the hope that it will be useful, |
| 13 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 |
GNU General Public License for more details. |
| 16 |
*/ |
| 17 |
|
| 18 |
#import <Cocoa/Cocoa.h> |
| 19 |
#include "MDRuby.h" |
| 20 |
|
| 21 |
#import "MyDocument.h" |
| 22 |
#import "MyMIDISequence.h" |
| 23 |
#import "MDObjects.h" |
| 24 |
|
| 25 |
#include <sys/time.h> /* for gettimeofday() */ |
| 26 |
#include <time.h> /* for clock() */ |
| 27 |
#include <signal.h> /* for sigaction() */ |
| 28 |
|
| 29 |
#include <pthread.h> /* for pthread implementation of interval timer */ |
| 30 |
|
| 31 |
#if __x86_64__ |
| 32 |
// Ugly hack, to avoid link error on __syscall() |
| 33 |
off_t __syscall(quad_t number, ...) |
| 34 |
{ |
| 35 |
return (off_t)(-1); // We don't need it anyway |
| 36 |
} |
| 37 |
#endif |
| 38 |
|
| 39 |
// Global variables |
| 40 |
int gRubyRunLevel = 0; |
| 41 |
int gRubyIsCheckingInterrupt = 0; |
| 42 |
|
| 43 |
VALUE gRubyBacktrace; |
| 44 |
VALUE gRubyErrorHistory; |
| 45 |
|
| 46 |
#pragma mark ====== Utility function ====== |
| 47 |
|
| 48 |
/* |
| 49 |
* Utility function |
| 50 |
* Get ary[i] by calling "[]" method |
| 51 |
*/ |
| 52 |
VALUE |
| 53 |
Ruby_ObjectAtIndex(VALUE ary, int idx) |
| 54 |
{ |
| 55 |
static ID index_method = 0; |
| 56 |
if (TYPE(ary) == T_ARRAY) { |
| 57 |
int len = (int)RARRAY_LEN(ary); |
| 58 |
if (idx >= 0 && idx < len) |
| 59 |
return (RARRAY_PTR(ary))[idx]; |
| 60 |
else return Qnil; |
| 61 |
} |
| 62 |
if (index_method == 0) |
| 63 |
index_method = rb_intern("[]"); |
| 64 |
return rb_funcall(ary, index_method, 1, INT2NUM(idx)); |
| 65 |
} |
| 66 |
|
| 67 |
char * |
| 68 |
Ruby_FileStringValuePtr(VALUE *valp) |
| 69 |
{ |
| 70 |
#if WINDOWS |
| 71 |
char *p = strdup(StringValuePtr(*valp)); |
| 72 |
translate_char(p, '/', '����'); |
| 73 |
*valp = rb_str_new2(p); |
| 74 |
free(p); |
| 75 |
return StringValuePtr(*valp); |
| 76 |
#else |
| 77 |
return StringValuePtr(*valp); |
| 78 |
#endif |
| 79 |
} |
| 80 |
|
| 81 |
VALUE |
| 82 |
Ruby_NewFileStringValue(const char *fstr) |
| 83 |
{ |
| 84 |
#if WINDOWS |
| 85 |
VALUE retval; |
| 86 |
char *p = strdup(fstr); |
| 87 |
translate_char(p, '����', '/'); |
| 88 |
retval = rb_str_new2(p); |
| 89 |
free(p); |
| 90 |
return retval; |
| 91 |
#else |
| 92 |
return rb_str_new2(fstr); |
| 93 |
#endif |
| 94 |
} |
| 95 |
|
| 96 |
char * |
| 97 |
Ruby_EncodedStringValuePtr(VALUE *valp) |
| 98 |
{ |
| 99 |
rb_string_value(valp); |
| 100 |
*valp = rb_str_encode(*valp, rb_enc_from_encoding(rb_default_external_encoding()), 0, Qnil); |
| 101 |
return RSTRING_PTR(*valp); |
| 102 |
} |
| 103 |
|
| 104 |
VALUE |
| 105 |
Ruby_NewEncodedStringValue(const char *str, int len) |
| 106 |
{ |
| 107 |
if (len <= 0) |
| 108 |
len = (int)strlen(str); |
| 109 |
return rb_enc_str_new(str, len, rb_default_external_encoding()); |
| 110 |
} |
| 111 |
|
| 112 |
VALUE |
| 113 |
Ruby_ObjToStringObj(VALUE val) |
| 114 |
{ |
| 115 |
switch (TYPE(val)) { |
| 116 |
case T_STRING: |
| 117 |
return val; |
| 118 |
case T_SYMBOL: |
| 119 |
return rb_str_new2(rb_id2name(SYM2ID(val))); |
| 120 |
default: |
| 121 |
return rb_str_to_str(val); |
| 122 |
} |
| 123 |
} |
| 124 |
|
| 125 |
void |
| 126 |
Ruby_getVersionStrings(const char **version, const char **copyright) |
| 127 |
{ |
| 128 |
static char *s_ruby_copyright; |
| 129 |
*version = ruby_version; |
| 130 |
if (s_ruby_copyright == NULL) |
| 131 |
asprintf(&s_ruby_copyright, "Copyright (C) %d-%d %s", RUBY_BIRTH_YEAR, 2013, RUBY_AUTHOR); |
| 132 |
*copyright = s_ruby_copyright; |
| 133 |
} |
| 134 |
|
| 135 |
|
| 136 |
#pragma mark ====== Message output ====== |
| 137 |
|
| 138 |
/* |
| 139 |
* call-seq: |
| 140 |
* MessageOutput.write(str) |
| 141 |
* |
| 142 |
* Put the message in the main text view. |
| 143 |
*/ |
| 144 |
static VALUE |
| 145 |
s_MessageOutput_Write(VALUE self, VALUE str) |
| 146 |
{ |
| 147 |
int n = MyAppCallback_showScriptMessage("%s", StringValuePtr(str)); |
| 148 |
return INT2NUM(n); |
| 149 |
} |
| 150 |
|
| 151 |
/* |
| 152 |
* call-seq: |
| 153 |
* message_box(str, title, button = nil, icon = :info) |
| 154 |
* |
| 155 |
* Show a message box. |
| 156 |
* Buttons: nil (ok and cancel), :ok (ok only), :cancel (cancel only) |
| 157 |
* Icon: :info, :warning, :error |
| 158 |
*/ |
| 159 |
static VALUE |
| 160 |
s_Kernel_MessageBox(int argc, VALUE *argv, VALUE self) |
| 161 |
{ |
| 162 |
char *str, *title, *s; |
| 163 |
int buttons, icon; |
| 164 |
VALUE sval, tval, bval, ival; |
| 165 |
rb_scan_args(argc, argv, "22", &sval, &tval, &bval, &ival); |
| 166 |
str = StringValuePtr(sval); |
| 167 |
title = StringValuePtr(tval); |
| 168 |
if (bval != Qnil) { |
| 169 |
bval = Ruby_ObjToStringObj(bval); |
| 170 |
s = RSTRING_PTR(bval); |
| 171 |
if (strncmp(s, "ok", 2) == 0) |
| 172 |
buttons = 1; |
| 173 |
else if (strncmp(s, "cancel", 6) == 0) |
| 174 |
buttons = 2; |
| 175 |
else |
| 176 |
rb_raise(rb_eStandardError, "the button specification should be either nil, :ok or :cancel"); |
| 177 |
} else buttons = 3; |
| 178 |
if (ival != Qnil) { |
| 179 |
ival = Ruby_ObjToStringObj(ival); |
| 180 |
s = RSTRING_PTR(ival); |
| 181 |
if (strncmp(s, "info", 4) == 0) |
| 182 |
icon = 1; |
| 183 |
else if (strncmp(s, "warn", 4) == 0) |
| 184 |
icon = 2; |
| 185 |
else if (strncmp(s, "err", 3) == 0) |
| 186 |
icon = 3; |
| 187 |
else |
| 188 |
rb_raise(rb_eStandardError, "the icon specification should be either :info, :warning or :error"); |
| 189 |
} else icon = 1; |
| 190 |
MyAppCallback_messageBox(str, title, buttons, icon); |
| 191 |
return Qnil; |
| 192 |
} |
| 193 |
|
| 194 |
#pragma mark ====== Track key events ====== |
| 195 |
|
| 196 |
/* User interrupt handling |
| 197 |
* User interrupt (command-period on Mac OS) is handled by periodic polling of |
| 198 |
* key events. This polling should only be enabled during "normal" execution |
| 199 |
* of scripts and must be disabled when the rest of the application (or Ruby |
| 200 |
* script itself) is handling GUI. This is ensured by appropriate calls to |
| 201 |
* enable_interrupt and disable_interrupt. */ |
| 202 |
|
| 203 |
static VALUE s_interrupt_flag = Qfalse; |
| 204 |
|
| 205 |
static VALUE |
| 206 |
s_ShowProgressPanel(int argc, VALUE *argv, VALUE self) |
| 207 |
{ |
| 208 |
volatile VALUE message; |
| 209 |
const char *p; |
| 210 |
if (Ruby_GetInterruptFlag() == Qtrue) { |
| 211 |
rb_scan_args(argc, argv, "01", &message); |
| 212 |
if (message != Qnil) |
| 213 |
p = StringValuePtr(message); |
| 214 |
else |
| 215 |
p = NULL; |
| 216 |
MyAppCallback_showProgressPanel(p); |
| 217 |
} |
| 218 |
return Qnil; |
| 219 |
} |
| 220 |
|
| 221 |
static VALUE |
| 222 |
s_HideProgressPanel(VALUE self) |
| 223 |
{ |
| 224 |
MyAppCallback_hideProgressPanel(); |
| 225 |
return Qnil; |
| 226 |
} |
| 227 |
|
| 228 |
static VALUE |
| 229 |
s_SetProgressValue(VALUE self, VALUE val) |
| 230 |
{ |
| 231 |
double dval = NUM2DBL(rb_Float(val)); |
| 232 |
MyAppCallback_setProgressValue(dval); |
| 233 |
return Qnil; |
| 234 |
} |
| 235 |
|
| 236 |
static VALUE |
| 237 |
s_SetProgressMessage(VALUE self, VALUE msg) |
| 238 |
{ |
| 239 |
const char *p; |
| 240 |
if (msg == Qnil) |
| 241 |
p = NULL; |
| 242 |
else p = StringValuePtr(msg); |
| 243 |
MyAppCallback_setProgressMessage(p); |
| 244 |
return Qnil; |
| 245 |
} |
| 246 |
|
| 247 |
static VALUE |
| 248 |
s_SetInterruptFlag(VALUE self, VALUE val) |
| 249 |
{ |
| 250 |
VALUE oldval; |
| 251 |
if (val != Qundef) { |
| 252 |
if (val == Qfalse || val == Qnil) |
| 253 |
val = Qfalse; |
| 254 |
else val = Qtrue; |
| 255 |
} |
| 256 |
oldval = s_interrupt_flag; |
| 257 |
if (val != Qundef) { |
| 258 |
s_interrupt_flag = val; |
| 259 |
if (val == Qfalse) { |
| 260 |
s_HideProgressPanel(self); |
| 261 |
} |
| 262 |
} |
| 263 |
return oldval; |
| 264 |
} |
| 265 |
|
| 266 |
static VALUE |
| 267 |
s_GetInterruptFlag(VALUE self) |
| 268 |
{ |
| 269 |
return s_SetInterruptFlag(self, Qundef); |
| 270 |
} |
| 271 |
|
| 272 |
/* |
| 273 |
static VALUE |
| 274 |
s_Ruby_CallMethod(VALUE val) |
| 275 |
{ |
| 276 |
void **ptr = (void **)val; |
| 277 |
VALUE receiver = (VALUE)ptr[0]; |
| 278 |
ID method_id = (ID)ptr[1]; |
| 279 |
VALUE args = (VALUE)ptr[2]; |
| 280 |
VALUE retval; |
| 281 |
if (method_id == 0) { |
| 282 |
// args should be a string, which is evaluated |
| 283 |
if (receiver == Qnil) { |
| 284 |
retval = rb_eval_string(StringValuePtr(args)); |
| 285 |
} else { |
| 286 |
retval = rb_obj_instance_eval(1, &args, receiver); |
| 287 |
} |
| 288 |
} else { |
| 289 |
// args should be an array of arguments |
| 290 |
retval = rb_apply(receiver, method_id, args); |
| 291 |
} |
| 292 |
return retval; |
| 293 |
} |
| 294 |
*/ |
| 295 |
/* |
| 296 |
VALUE |
| 297 |
Ruby_CallMethodWithInterrupt(VALUE receiver, ID method_id, VALUE args, int *status) |
| 298 |
{ |
| 299 |
VALUE retval, save_interrupt_flag; |
| 300 |
void *ptr[3]; |
| 301 |
save_interrupt_flag = s_SetInterruptFlag(Qnil, Qtrue); |
| 302 |
ptr[0] = (void *)receiver; |
| 303 |
ptr[1] = (void *)method_id; |
| 304 |
ptr[2] = (void *)args; |
| 305 |
retval = rb_protect(s_Ruby_CallMethod, (VALUE)ptr, status); |
| 306 |
s_SetInterruptFlag(Qnil, save_interrupt_flag); |
| 307 |
MyAppCallback_hideProgressPanel(); // In case when the progress panel is still onscreen |
| 308 |
return retval; |
| 309 |
} |
| 310 |
*/ |
| 311 |
|
| 312 |
VALUE |
| 313 |
Ruby_SetInterruptFlag(VALUE val) |
| 314 |
{ |
| 315 |
return s_SetInterruptFlag(Qnil, val); |
| 316 |
} |
| 317 |
|
| 318 |
VALUE |
| 319 |
Ruby_GetInterruptFlag(void) |
| 320 |
{ |
| 321 |
return s_SetInterruptFlag(Qnil, Qundef); |
| 322 |
} |
| 323 |
|
| 324 |
/* |
| 325 |
* call-seq: |
| 326 |
* check_interrupt -> integer |
| 327 |
* |
| 328 |
* Returns 1 if interrupted, 0 if not, -1 if interrupt is disabled. |
| 329 |
*/ |
| 330 |
static VALUE |
| 331 |
s_Kernel_CheckInterrupt(VALUE self) |
| 332 |
{ |
| 333 |
if (Ruby_GetInterruptFlag() == Qfalse) |
| 334 |
return INT2NUM(-1); |
| 335 |
else if (MyAppCallback_checkInterrupt()) |
| 336 |
return INT2NUM(1); |
| 337 |
else return INT2NUM(0); |
| 338 |
} |
| 339 |
|
| 340 |
#define USE_SIGALRM 0 |
| 341 |
|
| 342 |
static volatile uint32_t sITimerCount = 0; |
| 343 |
|
| 344 |
#if !USE_SIGALRM |
| 345 |
static pthread_t sTimerThread; |
| 346 |
|
| 347 |
/* -1: uninitiated; 0: active, 1: inactive, -2: request to terminate */ |
| 348 |
static volatile signed char sTimerFlag = -1; |
| 349 |
|
| 350 |
static void * |
| 351 |
s_TimerThreadEntry(void *param) |
| 352 |
{ |
| 353 |
while (1) { |
| 354 |
my_usleep(50000); |
| 355 |
if (sTimerFlag == 0) |
| 356 |
sITimerCount++; |
| 357 |
else if (sTimerFlag == -2) |
| 358 |
break; |
| 359 |
} |
| 360 |
return NULL; |
| 361 |
} |
| 362 |
|
| 363 |
#else |
| 364 |
static void |
| 365 |
s_SignalAction(int n) |
| 366 |
{ |
| 367 |
sITimerCount++; |
| 368 |
} |
| 369 |
#endif |
| 370 |
static void |
| 371 |
s_SetIntervalTimer(int n) |
| 372 |
{ |
| 373 |
#if !USE_SIGALRM |
| 374 |
if (n == 0) { |
| 375 |
if (sTimerFlag == -1) { |
| 376 |
int status = pthread_create(&sTimerThread, NULL, s_TimerThreadEntry, NULL); |
| 377 |
if (status != 0) { |
| 378 |
fprintf(stderr, "pthread_create failed while setting Ruby interval timer: status = %d��n", status); |
| 379 |
} |
| 380 |
} |
| 381 |
sTimerFlag = 0; /* Active */ |
| 382 |
} else if (sTimerFlag != -1) |
| 383 |
sTimerFlag = 1; /* Inactive */ |
| 384 |
#else |
| 385 |
static struct itimerval sOldValue; |
| 386 |
static struct sigaction sOldAction; |
| 387 |
struct itimerval val; |
| 388 |
struct sigaction act; |
| 389 |
if (n == 0) { |
| 390 |
sITimerCount = 0; |
| 391 |
act.sa_handler = s_SignalAction; |
| 392 |
act.sa_mask = 0; |
| 393 |
act.sa_flags = 0; |
| 394 |
sigaction(SIGALRM, &act, &sOldAction); |
| 395 |
val.it_value.tv_sec = 0; |
| 396 |
val.it_value.tv_usec = 50000; /* 50 msec */ |
| 397 |
val.it_interval.tv_sec = 0; |
| 398 |
val.it_interval.tv_usec = 50000; |
| 399 |
setitimer(ITIMER_REAL, &val, &sOldValue); |
| 400 |
} else { |
| 401 |
setitimer(ITIMER_REAL, &sOldValue, &val); |
| 402 |
sigaction(SIGALRM, &sOldAction, &act); |
| 403 |
} |
| 404 |
#endif |
| 405 |
} |
| 406 |
|
| 407 |
static void |
| 408 |
s_Event_Callback(rb_event_flag_t evflag, VALUE data, VALUE self, ID mid, VALUE klass) |
| 409 |
{ |
| 410 |
if (s_interrupt_flag != Qfalse) { |
| 411 |
static uint32_t sLastTime = 0; |
| 412 |
uint32_t currentTime; |
| 413 |
int flag; |
| 414 |
currentTime = sITimerCount; |
| 415 |
if (currentTime != sLastTime) { |
| 416 |
sLastTime = currentTime; |
| 417 |
gRubyIsCheckingInterrupt = 1; |
| 418 |
flag = MyAppCallback_checkInterrupt(); |
| 419 |
gRubyIsCheckingInterrupt = 0; |
| 420 |
if (flag) { |
| 421 |
s_SetInterruptFlag(Qnil, Qfalse); |
| 422 |
rb_interrupt(); |
| 423 |
} |
| 424 |
} |
| 425 |
} |
| 426 |
} |
| 427 |
|
| 428 |
#pragma mark ====== Menu handling ====== |
| 429 |
|
| 430 |
/* Array of menu validators (to avoid garbage collection) */ |
| 431 |
static VALUE sValidatorList = Qnil; |
| 432 |
|
| 433 |
/* |
| 434 |
* call-seq: |
| 435 |
* register_menu(title, method, validator = nil) |
| 436 |
* |
| 437 |
* Register the method (specified as a symbol) in the script menu. |
| 438 |
* The method must be either an instance method of Sequence with no argument, |
| 439 |
* or a class method of Sequence with one argument (the current Sequence). |
| 440 |
* The menu associated with the class method can be invoked even when no document |
| 441 |
* is open (the argument is set to Qnil in this case). On the other hand, the |
| 442 |
* menu associated with the instance method can only be invoked when at least one |
| 443 |
* document is active. |
| 444 |
* Validator controls how the menu item is activated. If it is nil, then |
| 445 |
* the menu is either 'always activated' (when method is a class method) or 'activated |
| 446 |
* if there is an open document' (when method is an instance method). If it is a numeric |
| 447 |
* 1 (one), then the menu is activated when there is an open document and at least one |
| 448 |
* event is selected (in any track). Otherwise, the validator should be a callable |
| 449 |
* object with one MRSequence argument, and if it returns 'true' (in Ruby sense) |
| 450 |
* then the menu is activated. |
| 451 |
*/ |
| 452 |
static VALUE |
| 453 |
s_Kernel_RegisterMenu(int argc, VALUE *argv, VALUE self) |
| 454 |
{ |
| 455 |
VALUE title, method, validator; |
| 456 |
rb_scan_args(argc, argv, "21", &title, &method, &validator); |
| 457 |
if (TYPE(method) == T_SYMBOL) { |
| 458 |
method = rb_funcall(method, rb_intern("to_s"), 0); |
| 459 |
} |
| 460 |
if (validator != Qnil && !FIXNUM_P(validator)) { |
| 461 |
if (sValidatorList == Qnil) { |
| 462 |
sValidatorList = rb_ary_new(); |
| 463 |
rb_define_variable("_validator_list", &sValidatorList); |
| 464 |
} |
| 465 |
rb_ary_push(sValidatorList, validator); |
| 466 |
} |
| 467 |
MyAppCallback_registerScriptMenu(StringValuePtr(method), StringValuePtr(title), (int32_t)validator); |
| 468 |
return self; |
| 469 |
} |
| 470 |
|
| 471 |
static VALUE |
| 472 |
s_MDRuby_methodType_sub(VALUE data) |
| 473 |
{ |
| 474 |
const char **p = (const char **)data; |
| 475 |
VALUE klass = rb_const_get(rb_cObject, rb_intern(p[0])); |
| 476 |
ID mid = rb_intern(p[1]); |
| 477 |
int ival; |
| 478 |
if (rb_funcall(klass, rb_intern("method_defined?"), 1, ID2SYM(mid)) != Qfalse) |
| 479 |
ival = 1; |
| 480 |
else if (rb_respond_to(klass, mid)) |
| 481 |
ival = 2; |
| 482 |
else ival = 0; |
| 483 |
return INT2FIX(ival); |
| 484 |
} |
| 485 |
|
| 486 |
/* Returns 1 if the class defines the instance method with the given name, 2 if the class |
| 487 |
has the singleton method (class method) with the given name, 0 otherwise. */ |
| 488 |
int |
| 489 |
Ruby_methodType(const char *className, const char *methodName) |
| 490 |
{ |
| 491 |
int status; |
| 492 |
VALUE retval; |
| 493 |
const char *p[2]; |
| 494 |
p[0] = className; |
| 495 |
p[1] = methodName; |
| 496 |
retval = rb_protect(s_MDRuby_methodType_sub, (VALUE)p, &status); |
| 497 |
if (status == 0) |
| 498 |
return FIX2INT(retval); |
| 499 |
else return 0; |
| 500 |
} |
| 501 |
|
| 502 |
static VALUE |
| 503 |
s_Ruby_callValidatorForDocument(VALUE data) |
| 504 |
{ |
| 505 |
VALUE *v = (VALUE *)data; |
| 506 |
MyDocument *doc = (MyDocument *)v[1]; |
| 507 |
if (v[0] == Qnil) { |
| 508 |
/* No validator: default behavior (it is already handled by MyDocument) */ |
| 509 |
return Qtrue; |
| 510 |
} else if (v[0] == INT2FIX(0)) { |
| 511 |
/* Valid if document is open */ |
| 512 |
return (doc != NULL ? Qtrue : Qfalse); |
| 513 |
} else if (v[0] == INT2FIX(1)) { |
| 514 |
/* Valid if document is open and some events are selected */ |
| 515 |
int32_t n, c; |
| 516 |
if (doc == NULL) |
| 517 |
return Qfalse; |
| 518 |
c = [[doc myMIDISequence] trackCount]; |
| 519 |
for (n = 0; n < c; n++) { |
| 520 |
IntGroup *pset = [[doc selectionOfTrack:n] pointSet]; |
| 521 |
if (IntGroupGetCount(pset) > 0) |
| 522 |
break; |
| 523 |
} |
| 524 |
return (n < c ? Qtrue : Qfalse); |
| 525 |
} |
| 526 |
return rb_funcall(v[0], rb_intern("call"), 1, (doc == NULL ? Qnil : MRSequenceFromMyDocument(doc))); |
| 527 |
} |
| 528 |
|
| 529 |
int |
| 530 |
Ruby_callValidatorForDocument(int32_t validator, void *doc) |
| 531 |
{ |
| 532 |
VALUE v[3]; |
| 533 |
VALUE retval; |
| 534 |
int status; |
| 535 |
v[0] = (VALUE)validator; |
| 536 |
v[1] = (VALUE)doc; |
| 537 |
retval = rb_protect(s_Ruby_callValidatorForDocument, (VALUE)&v, &status); |
| 538 |
if (status == 0 && RTEST(retval)) |
| 539 |
return 1; |
| 540 |
else return 0; |
| 541 |
} |
| 542 |
|
| 543 |
/* |
| 544 |
* call-seq: |
| 545 |
* execute_script_file(fname) |
| 546 |
* |
| 547 |
* Execute the script in the given file. If a molecule is active, then |
| 548 |
* the script is evaluated as Molecule.current.instance_eval(script). |
| 549 |
* Before entering the script, the current directory is set to the parent |
| 550 |
* directory of the script. |
| 551 |
*/ |
| 552 |
static VALUE |
| 553 |
s_Kernel_ExecuteScript(VALUE self, VALUE fname) |
| 554 |
{ |
| 555 |
int status; |
| 556 |
VALUE retval = (VALUE)MyAppCallback_executeScriptFromFile(StringValuePtr(fname), &status); |
| 557 |
if (status != 0) |
| 558 |
rb_jump_tag(status); |
| 559 |
return retval; |
| 560 |
} |
| 561 |
|
| 562 |
#pragma mark ====== User defaults ====== |
| 563 |
|
| 564 |
/* |
| 565 |
* call-seq: |
| 566 |
* Kernel.get_global_settings(key) |
| 567 |
* |
| 568 |
* Get a setting data for key from the application preferences. |
| 569 |
*/ |
| 570 |
static VALUE |
| 571 |
s_Kernel_GetGlobalSettings(VALUE self, VALUE key) |
| 572 |
{ |
| 573 |
const char *p = MyAppCallback_getGlobalSettings(StringValuePtr(key)); |
| 574 |
if (p != NULL) { |
| 575 |
return rb_str_new2(p); |
| 576 |
} else return Qnil; |
| 577 |
} |
| 578 |
|
| 579 |
/* |
| 580 |
* call-seq: |
| 581 |
* Kernel.set_global_settings(key, value) |
| 582 |
* |
| 583 |
* Set a setting data for key to the application preferences. |
| 584 |
*/ |
| 585 |
static VALUE |
| 586 |
s_Kernel_SetGlobalSettings(VALUE self, VALUE key, VALUE value) |
| 587 |
{ |
| 588 |
key = rb_obj_as_string(key); |
| 589 |
value = rb_obj_as_string(value); |
| 590 |
MyAppCallback_setGlobalSettings(StringValuePtr(key), StringValuePtr(value)); |
| 591 |
return value; |
| 592 |
} |
| 593 |
|
| 594 |
/* |
| 595 |
* call-seq: |
| 596 |
* Kernel.sanity_check(boolean = current value) |
| 597 |
* |
| 598 |
* Set whether track data check is done after every editing operation |
| 599 |
*/ |
| 600 |
static VALUE |
| 601 |
s_Kernel_SanityCheck(int argc, VALUE *argv, VALUE self) |
| 602 |
{ |
| 603 |
extern int gMyDocumentSanityCheck; |
| 604 |
VALUE fval = Qnil; |
| 605 |
if (argc > 0) |
| 606 |
fval = argv[0]; |
| 607 |
if (fval != Qnil) { |
| 608 |
gMyDocumentSanityCheck = (RTEST(fval) ? 1 : 0); |
| 609 |
} |
| 610 |
return gMyDocumentSanityCheck ? Qtrue :Qfalse; |
| 611 |
} |
| 612 |
|
| 613 |
|
| 614 |
#pragma mark ====== Utility functions (protected funcall) ====== |
| 615 |
|
| 616 |
struct Ruby_funcall2_record { |
| 617 |
VALUE recv; |
| 618 |
ID mid; |
| 619 |
int argc; |
| 620 |
VALUE *argv; |
| 621 |
}; |
| 622 |
|
| 623 |
static VALUE |
| 624 |
s_Ruby_funcall2_sub(VALUE data) |
| 625 |
{ |
| 626 |
struct Ruby_funcall2_record *rp = (struct Ruby_funcall2_record *)data; |
| 627 |
return rb_funcall2(rp->recv, rp->mid, rp->argc, rp->argv); |
| 628 |
} |
| 629 |
|
| 630 |
VALUE |
| 631 |
Ruby_funcall2_protect(VALUE recv, ID mid, int argc, VALUE *argv, int *status) |
| 632 |
{ |
| 633 |
struct Ruby_funcall2_record rec; |
| 634 |
rec.recv = recv; |
| 635 |
rec.mid = mid; |
| 636 |
rec.argc = argc; |
| 637 |
rec.argv = argv; |
| 638 |
return rb_protect(s_Ruby_funcall2_sub, (VALUE)&rec, status); |
| 639 |
} |
| 640 |
|
| 641 |
#pragma mark ====== Initialize class ====== |
| 642 |
|
| 643 |
void |
| 644 |
MRCoreInitClass(void) |
| 645 |
{ |
| 646 |
/* module Kernel */ |
| 647 |
rb_define_method(rb_mKernel, "check_interrupt", s_Kernel_CheckInterrupt, 0); |
| 648 |
rb_define_method(rb_mKernel, "get_interrupt_flag", s_GetInterruptFlag, 0); |
| 649 |
rb_define_method(rb_mKernel, "set_interrupt_flag", s_SetInterruptFlag, 1); |
| 650 |
rb_define_method(rb_mKernel, "show_progress_panel", s_ShowProgressPanel, -1); |
| 651 |
rb_define_method(rb_mKernel, "hide_progress_panel", s_HideProgressPanel, 0); |
| 652 |
rb_define_method(rb_mKernel, "set_progress_value", s_SetProgressValue, 1); |
| 653 |
rb_define_method(rb_mKernel, "set_progress_message", s_SetProgressMessage, 1); |
| 654 |
rb_define_method(rb_mKernel, "register_menu", s_Kernel_RegisterMenu, -1); |
| 655 |
rb_define_method(rb_mKernel, "get_global_settings", s_Kernel_GetGlobalSettings, 1); |
| 656 |
rb_define_method(rb_mKernel, "set_global_settings", s_Kernel_SetGlobalSettings, 2); |
| 657 |
rb_define_method(rb_mKernel, "execute_script", s_Kernel_ExecuteScript, 1); |
| 658 |
rb_define_method(rb_mKernel, "sanity_check", s_Kernel_SanityCheck, -1); |
| 659 |
rb_define_method(rb_mKernel, "message_box", s_Kernel_MessageBox, -1); |
| 660 |
} |
| 661 |
|
| 662 |
#pragma mark ====== External functions ====== |
| 663 |
|
| 664 |
typedef struct RubyArgsRecord { |
| 665 |
void *doc; /* A pointer to document */ |
| 666 |
const char *script; /* A method name or a script */ |
| 667 |
int type; /* 0: script, 1: ordinary method, 2: singleton method */ |
| 668 |
const char *argfmt; /* Argument format string */ |
| 669 |
const char *fname; /* Not used yet */ |
| 670 |
va_list ap; /* Argument list */ |
| 671 |
} RubyArgsRecord; |
| 672 |
|
| 673 |
static VALUE s_ruby_top_self = Qfalse; |
| 674 |
static VALUE s_ruby_get_binding_for_document = Qfalse; |
| 675 |
static VALUE s_ruby_export_local_variables = Qfalse; |
| 676 |
|
| 677 |
static VALUE |
| 678 |
s_evalRubyScriptOnDocumentSub(VALUE val) |
| 679 |
{ |
| 680 |
RubyArgsRecord *arec = (RubyArgsRecord *)val; |
| 681 |
VALUE sval, fnval, lnval, retval; |
| 682 |
VALUE binding; |
| 683 |
|
| 684 |
/* Clear the error information (store in the history array if necessary) */ |
| 685 |
sval = rb_errinfo(); |
| 686 |
if (sval != Qnil) { |
| 687 |
rb_eval_string("$error_history.push([$!.to_s, $!.backtrace])"); |
| 688 |
rb_set_errinfo(Qnil); |
| 689 |
} |
| 690 |
|
| 691 |
if (s_ruby_top_self == Qfalse) { |
| 692 |
s_ruby_top_self = rb_eval_string("eval(\"self\",TOPLEVEL_BINDING)"); |
| 693 |
} |
| 694 |
if (s_ruby_get_binding_for_document == Qfalse) { |
| 695 |
const char *s1 = |
| 696 |
"lambda { |_doc_, _bind_| \n" |
| 697 |
" _proc_ = eval(\"lambda { |__doc__| __doc__.instance_eval { binding } } \", _bind_) \n" |
| 698 |
" _proc_.call(_doc_) } "; |
| 699 |
s_ruby_get_binding_for_document = rb_eval_string(s1); |
| 700 |
rb_define_variable("_get_binding_for_document", &s_ruby_get_binding_for_document); |
| 701 |
} |
| 702 |
if (s_ruby_export_local_variables == Qfalse) { |
| 703 |
const char *s2 = |
| 704 |
"lambda { |_bind_| \n" |
| 705 |
" # find local variables newly defined in _bind_ \n" |
| 706 |
" _a_ = _bind_.eval(\"local_variables\") - TOPLEVEL_BINDING.eval(\"local_variables\"); \n" |
| 707 |
" _a_.each { |_vsym_| \n" |
| 708 |
" _vname_ = _vsym_.to_s \n" |
| 709 |
" _vval_ = _bind_.eval(_vname_) \n" |
| 710 |
" # Define local variable \n" |
| 711 |
" TOPLEVEL_BINDING.eval(_vname_ + \" = nil\") \n" |
| 712 |
" # Then set value \n" |
| 713 |
" TOPLEVEL_BINDING.eval(\"lambda { |_m_| \" + _vname_ + \" = _m_ }\").call(_vval_) \n" |
| 714 |
" } \n" |
| 715 |
"}"; |
| 716 |
s_ruby_export_local_variables = rb_eval_string(s2); |
| 717 |
rb_define_variable("_export_local_variables", &s_ruby_export_local_variables); |
| 718 |
} |
| 719 |
if (arec->fname == NULL) { |
| 720 |
char *scr; |
| 721 |
/* String literal: we need to specify string encoding */ |
| 722 |
asprintf(&scr, "#coding:utf-8\n%s", arec->script); |
| 723 |
sval = rb_str_new2(scr); |
| 724 |
free(scr); |
| 725 |
fnval = rb_str_new2("(eval)"); |
| 726 |
lnval = INT2FIX(0); |
| 727 |
} else { |
| 728 |
sval = rb_str_new2(arec->script); |
| 729 |
fnval = Ruby_NewFileStringValue(arec->fname); |
| 730 |
lnval = INT2FIX(1); |
| 731 |
} |
| 732 |
binding = rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")); |
| 733 |
if (arec->doc != NULL) { |
| 734 |
VALUE mval = MRSequenceFromMyDocument(arec->doc); |
| 735 |
binding = rb_funcall(s_ruby_get_binding_for_document, rb_intern("call"), 2, mval, binding); |
| 736 |
} |
| 737 |
retval = rb_funcall(binding, rb_intern("eval"), 3, sval, fnval, lnval); |
| 738 |
if (arec->doc != NULL) { |
| 739 |
rb_funcall(s_ruby_export_local_variables, rb_intern("call"), 1, binding); |
| 740 |
} |
| 741 |
return retval; |
| 742 |
} |
| 743 |
|
| 744 |
RubyValue |
| 745 |
Ruby_evalRubyScriptOnDocument(const char *script, void *doc, int *status) |
| 746 |
{ |
| 747 |
RubyValue retval; |
| 748 |
RubyArgsRecord rec; |
| 749 |
VALUE save_interrupt_flag; |
| 750 |
/* char *save_ruby_sourcefile; |
| 751 |
int save_ruby_sourceline; */ |
| 752 |
if (gRubyIsCheckingInterrupt) { |
| 753 |
// TODO: Show alert |
| 754 |
// MolActionAlertRubyIsRunning(); |
| 755 |
*status = -1; |
| 756 |
return (RubyValue)Qnil; |
| 757 |
} |
| 758 |
gRubyRunLevel++; |
| 759 |
memset(&rec, 0, sizeof(rec)); |
| 760 |
rec.doc = doc; |
| 761 |
rec.script = script; |
| 762 |
rec.type = 0; |
| 763 |
// TODO: support fname? |
| 764 |
// args[2] = (void *)fname; |
| 765 |
save_interrupt_flag = s_SetInterruptFlag(Qnil, Qtrue); |
| 766 |
retval = (RubyValue)rb_protect(s_evalRubyScriptOnDocumentSub, (VALUE)&rec, status); |
| 767 |
if (*status != 0) { |
| 768 |
/* Is this 'exit' exception? */ |
| 769 |
VALUE last_exception = rb_gv_get("$!"); |
| 770 |
if (rb_obj_is_kind_of(last_exception, rb_eSystemExit)) { |
| 771 |
/* Capture exit and return the status value */ |
| 772 |
retval = (RubyValue)rb_funcall(last_exception, rb_intern("status"), 0); |
| 773 |
*status = 0; |
| 774 |
rb_set_errinfo(Qnil); |
| 775 |
} |
| 776 |
} |
| 777 |
s_SetInterruptFlag(Qnil, save_interrupt_flag); |
| 778 |
gRubyRunLevel--; |
| 779 |
return retval; |
| 780 |
} |
| 781 |
|
| 782 |
static VALUE |
| 783 |
s_executeRubyOnDocument(VALUE vinfo) |
| 784 |
{ |
| 785 |
VALUE retval, args, aval, mval; |
| 786 |
int i, n; |
| 787 |
ID mid; |
| 788 |
char retfmt = 0; |
| 789 |
void *retp1, *retp2, *retp3; |
| 790 |
RubyArgsRecord *rp = (RubyArgsRecord *)vinfo; |
| 791 |
VALUE save_interrupt_flag; |
| 792 |
|
| 793 |
if (rp->type == 0) { |
| 794 |
/* Evaluate as string (no other arguments) */ |
| 795 |
save_interrupt_flag = s_SetInterruptFlag(Qnil, Qtrue); |
| 796 |
if (rp->doc == NULL) |
| 797 |
retval = rb_eval_string(rp->script); |
| 798 |
else { |
| 799 |
aval = rb_str_new2(rp->script); |
| 800 |
mval = MRSequenceFromMyDocument(rp->doc); |
| 801 |
retval = rb_obj_instance_eval(1, &aval, mval); |
| 802 |
} |
| 803 |
s_SetInterruptFlag(Qnil, save_interrupt_flag); |
| 804 |
return retval; |
| 805 |
} |
| 806 |
|
| 807 |
mval = MRSequenceFromMyDocument(rp->doc); |
| 808 |
mid = rb_intern(rp->script); |
| 809 |
args = rb_ary_new(); |
| 810 |
save_interrupt_flag = s_SetInterruptFlag(Qnil, Qtrue); |
| 811 |
|
| 812 |
/* Analyse the argfmt */ |
| 813 |
if (rp->argfmt != NULL && rp->argfmt[0] != 0) { |
| 814 |
const char *p = rp->argfmt; |
| 815 |
int ival; |
| 816 |
/* va_list ap = rp->ap; *//* This assignment does not work on 64-bit build */ |
| 817 |
while (*p != 0) { |
| 818 |
switch (*p) { |
| 819 |
case 'b': |
| 820 |
ival = va_arg(rp->ap, int); |
| 821 |
aval = (ival ? Qtrue : Qfalse); |
| 822 |
break; |
| 823 |
case 'i': |
| 824 |
aval = INT2NUM(va_arg(rp->ap, int)); break; |
| 825 |
case 'l': |
| 826 |
aval = INT2NUM(va_arg(rp->ap, int32_t)); break; |
| 827 |
case 'q': |
| 828 |
aval = LL2NUM(va_arg(rp->ap, int64_t)); break; |
| 829 |
case 'd': |
| 830 |
aval = rb_float_new(va_arg(rp->ap, double)); break; |
| 831 |
case 's': |
| 832 |
aval = rb_str_new2(va_arg(rp->ap, const char *)); break; |
| 833 |
case 'a': /* ASCII characters (can contain NUL) */ |
| 834 |
n = va_arg(rp->ap, int); |
| 835 |
aval = rb_str_new(va_arg(rp->ap, const char *), n); |
| 836 |
break; |
| 837 |
case 'B': case 'I': case 'L': case 'Q': case 'D': case 'S': case 'A': { |
| 838 |
VALUE aaval; |
| 839 |
void *pp; |
| 840 |
n = va_arg(rp->ap, int); |
| 841 |
pp = va_arg(rp->ap, void *); |
| 842 |
aval = rb_ary_new2(n); |
| 843 |
for (i = 0; i < n; i++) { |
| 844 |
switch (*p) { |
| 845 |
case 'B': |
| 846 |
ival = ((int *)pp)[i]; |
| 847 |
aaval = (ival ? Qtrue : Qfalse); |
| 848 |
break; |
| 849 |
case 'I': |
| 850 |
aaval = INT2NUM(((int *)pp)[i]); break; |
| 851 |
case 'L': |
| 852 |
aaval = INT2NUM(((int32_t *)pp)[i]); break; |
| 853 |
case 'Q': |
| 854 |
aaval = LL2NUM(((int64_t *)pp)[i]); break; |
| 855 |
case 'D': |
| 856 |
aaval = rb_float_new(((double *)pp)[i]); break; |
| 857 |
case 'S': |
| 858 |
aaval = rb_str_new2(((const char **)pp)[i]); break; |
| 859 |
} |
| 860 |
rb_ary_push(aval, aaval); |
| 861 |
} |
| 862 |
break; |
| 863 |
} |
| 864 |
case ';': |
| 865 |
retfmt = *++p; |
| 866 |
retp1 = va_arg(rp->ap, void *); |
| 867 |
if (retfmt == 'a' || (retfmt >= 'A' && retfmt <= 'Z')) |
| 868 |
retp2 = va_arg(rp->ap, void *); |
| 869 |
goto out_of_loop; |
| 870 |
} /* end switch */ |
| 871 |
rb_ary_push(args, aval); |
| 872 |
p++; |
| 873 |
} /* end while */ |
| 874 |
} /* end if */ |
| 875 |
|
| 876 |
out_of_loop: |
| 877 |
if (rp->type == 2) { |
| 878 |
rb_ary_unshift(args, mval); |
| 879 |
retval = rb_apply(rb_cMRSequence, mid, args); |
| 880 |
} else { |
| 881 |
retval = rb_apply(mval, mid, args); |
| 882 |
} |
| 883 |
switch (retfmt) { |
| 884 |
case 'b': |
| 885 |
*((int *)retp1) = RTEST(retval); break; |
| 886 |
case 'i': |
| 887 |
*((int *)retp1) = NUM2INT(rb_Integer(retval)); break; |
| 888 |
case 'l': |
| 889 |
*((int32_t *)retp1) = NUM2INT(rb_Integer(retval)); break; |
| 890 |
case 'q': |
| 891 |
*((int64_t *)retp1) = NUM2LL(rb_Integer(retval)); break; |
| 892 |
case 'd': |
| 893 |
*((double *)retp1) = NUM2DBL(rb_Float(retval)); break; |
| 894 |
case 's': |
| 895 |
*((char **)retp1) = strdup(StringValuePtr(retval)); break; |
| 896 |
case 'a': |
| 897 |
retval = rb_str_to_str(retval); |
| 898 |
n = (int)RSTRING_LEN(retval); |
| 899 |
*((int *)retp1) = n; |
| 900 |
retp3 = malloc(n + 1); |
| 901 |
memmove(retp3, RSTRING_PTR(retval), n); |
| 902 |
((char *)retp3)[n] = 0; |
| 903 |
*((void **)retp2) = retp3; |
| 904 |
break; |
| 905 |
case 'B': case 'I': case 'L': case 'Q': case 'D': case 'S': |
| 906 |
switch (retfmt) { |
| 907 |
case 'B': case 'I': i = sizeof(int); break; |
| 908 |
case 'L': i = sizeof(int32_t); break; |
| 909 |
case 'Q': i = sizeof(int64_t); break; |
| 910 |
case 'D': i = sizeof(double); break; |
| 911 |
case 'S': i = sizeof(char *); break; |
| 912 |
} |
| 913 |
if (retval == Qnil) |
| 914 |
n = 0; |
| 915 |
else { |
| 916 |
retval = rb_ary_to_ary(retval); |
| 917 |
n = (int)RARRAY_LEN(retval); |
| 918 |
} |
| 919 |
*((int *)retp1) = n; |
| 920 |
if (n == 0) |
| 921 |
retp3 = NULL; |
| 922 |
else { |
| 923 |
retp3 = calloc(n, i); |
| 924 |
for (i = 0; i < n; i++) { |
| 925 |
aval = RARRAY_PTR(retval)[i]; |
| 926 |
switch (retfmt) { |
| 927 |
case 'B': ((int *)retp3)[i] = RTEST(aval); break; |
| 928 |
case 'I': ((int *)retp3)[i] = NUM2INT(rb_Integer(aval)); break; |
| 929 |
case 'L': ((int32_t *)retp3)[i] = NUM2INT(rb_Integer(aval)); break; |
| 930 |
case 'Q': ((int64_t *)retp3)[i] = NUM2LL(rb_Integer(aval)); break; |
| 931 |
case 'D': ((double *)retp3)[i] = NUM2DBL(rb_Float(aval)); break; |
| 932 |
case 'S': ((char **)retp3)[i] = strdup(StringValuePtr(aval)); break; |
| 933 |
} |
| 934 |
} |
| 935 |
} |
| 936 |
*((void **)retp2) = retp3; |
| 937 |
break; |
| 938 |
} |
| 939 |
s_SetInterruptFlag(Qnil, save_interrupt_flag); |
| 940 |
return retval; |
| 941 |
} |
| 942 |
|
| 943 |
/* argfmt: characters representing the arguments |
| 944 |
b: boolean (int), i: integer, l: int32_t integer, q: int64_t integer, |
| 945 |
d: double, s: string (const char *), |
| 946 |
B, I, L, Q, D, S: array of the above type (two arguments: number of values followed by a pointer) |
| 947 |
;X (X is one of the above types): return value; if the type is a simple type (represented by |
| 948 |
a lowercase character), one pointer (TYPE *) is required. If the type is an array type, |
| 949 |
two pointers (int * and TYPE **) are required. On returning a string or an array, the |
| 950 |
required storage are allocated by malloc() (i.e. the caller is responsible for free'ing them). |
| 951 |
If the return value is an array of strings, each string should be free'd. */ |
| 952 |
int |
| 953 |
Ruby_callMethodOfDocument(const char *name, void *document, int isSingleton, const char *argfmt, ...) |
| 954 |
{ |
| 955 |
RubyArgsRecord rec; |
| 956 |
int status; |
| 957 |
rec.doc = document; |
| 958 |
rec.script = name; |
| 959 |
rec.type = isSingleton + 1; |
| 960 |
rec.argfmt = argfmt; |
| 961 |
va_start(rec.ap, argfmt); |
| 962 |
rb_protect(s_executeRubyOnDocument, (VALUE)&rec, &status); |
| 963 |
return status; |
| 964 |
} |
| 965 |
|
| 966 |
int |
| 967 |
Ruby_showValue(RubyValue value, char **outValueString) |
| 968 |
{ |
| 969 |
VALUE val = (VALUE)value; |
| 970 |
if (gRubyIsCheckingInterrupt) { |
| 971 |
// TODO: show error message |
| 972 |
return 0; |
| 973 |
} |
| 974 |
if (val != Qnil) { |
| 975 |
int status; |
| 976 |
char *str; |
| 977 |
gRubyRunLevel++; |
| 978 |
val = rb_protect(rb_inspect, val, &status); |
| 979 |
gRubyRunLevel--; |
| 980 |
if (status != 0) |
| 981 |
return status; |
| 982 |
str = StringValuePtr(val); |
| 983 |
if (outValueString != NULL) |
| 984 |
*outValueString = strdup(str); |
| 985 |
MyAppCallback_showScriptMessage("%s", str); |
| 986 |
} else { |
| 987 |
if (outValueString != NULL) |
| 988 |
*outValueString = NULL; |
| 989 |
} |
| 990 |
return 0; |
| 991 |
} |
| 992 |
|
| 993 |
void |
| 994 |
Ruby_showError(int status) |
| 995 |
{ |
| 996 |
static const int tag_raise = 6; |
| 997 |
char *msg = NULL, *msg2; |
| 998 |
VALUE val, backtrace; |
| 999 |
int interrupted = 0; |
| 1000 |
if (status == tag_raise) { |
| 1001 |
VALUE errinfo = rb_errinfo(); |
| 1002 |
VALUE eclass = CLASS_OF(errinfo); |
| 1003 |
if (eclass == rb_eInterrupt) { |
| 1004 |
msg = "Interrupt"; |
| 1005 |
interrupted = 1; |
| 1006 |
} |
| 1007 |
} |
| 1008 |
gRubyRunLevel++; |
| 1009 |
backtrace = rb_eval_string_protect("$backtrace = $!.backtrace.join(\"\\n\")", &status); |
| 1010 |
if (msg == NULL) { |
| 1011 |
val = rb_eval_string_protect("$!.to_s", &status); |
| 1012 |
if (status == 0) |
| 1013 |
msg = RSTRING_PTR(val); |
| 1014 |
else msg = "(message not available)"; |
| 1015 |
} |
| 1016 |
asprintf(&msg2, "%s\n%s", msg, RSTRING_PTR(backtrace)); |
| 1017 |
MyAppCallback_messageBox(msg2, (interrupted == 0 ? "Ruby script error" : "Ruby script interrupted"), 0, 3); |
| 1018 |
free(msg2); |
| 1019 |
gRubyRunLevel--; |
| 1020 |
} |
| 1021 |
|
| 1022 |
void |
| 1023 |
Ruby_startup(void) |
| 1024 |
{ |
| 1025 |
VALUE val; |
| 1026 |
|
| 1027 |
/* Initialize Ruby interpreter */ |
| 1028 |
ruby_init(); |
| 1029 |
ruby_init_loadpath(); |
| 1030 |
ruby_script("MDRuby"); |
| 1031 |
|
| 1032 |
/* Define MDRuby classes */ |
| 1033 |
MREventSetInitClass(); |
| 1034 |
MRCoreInitClass(); |
| 1035 |
MRSequenceInitClass(); |
| 1036 |
MRTrackInitClass(); |
| 1037 |
MRPointerInitClass(); |
| 1038 |
RubyDialogInitClass(); |
| 1039 |
|
| 1040 |
/* Create an object for standard output and standard error */ |
| 1041 |
val = rb_obj_alloc(rb_cObject); |
| 1042 |
rb_define_singleton_method(val, "write", s_MessageOutput_Write, 1); |
| 1043 |
rb_gv_set("$stdout", val); |
| 1044 |
rb_gv_set("$stderr", val); |
| 1045 |
|
| 1046 |
/* Global variable to hold error information */ |
| 1047 |
rb_define_variable("$backtrace", &gRubyBacktrace); |
| 1048 |
rb_define_variable("$error_history", &gRubyErrorHistory); |
| 1049 |
gRubyErrorHistory = rb_ary_new(); |
| 1050 |
gRubyBacktrace = Qnil; |
| 1051 |
|
| 1052 |
/* Register interrupt check code */ |
| 1053 |
rb_add_event_hook(s_Event_Callback, RUBY_EVENT_ALL, Qnil); |
| 1054 |
|
| 1055 |
/* Start interval timer */ |
| 1056 |
s_SetIntervalTimer(0); |
| 1057 |
} |