Created
September 11, 2013 00:32
-
-
Save haileys/6517837 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/************************************************ | |
eval.c - | |
$Author: matz $ | |
$Date: 1996/12/25 08:54:45 $ | |
created at: Thu Jun 10 14:22:17 JST 1993 | |
Copyright (C) 1993-1997 Yukihiro Matsumoto | |
************************************************/ | |
#include "ruby.h" | |
#include "node.h" | |
#include "env.h" | |
#include "sig.h" | |
#include <stdio.h> | |
#include <setjmp.h> | |
#include "st.h" | |
#include "dln.h" | |
#ifdef HAVE_STRING_H | |
# include <string.h> | |
#else | |
char *strrchr(); | |
#endif | |
#ifndef setjmp | |
#ifdef HAVE__SETJMP | |
#define setjmp(env) _setjmp(env) | |
#define longjmp(env,val) _longjmp(env,val) | |
#endif | |
#endif | |
extern VALUE cData; | |
VALUE cProc; | |
static VALUE proc_call(); | |
static VALUE f_binding(); | |
#define CACHE_SIZE 0x200 | |
#define CACHE_MASK 0x1ff | |
#define EXPR1(c,m) ((((int)(c)>>3)^(m))&CACHE_MASK) | |
struct cache_entry { /* method hash table. */ | |
ID mid; /* method's id */ | |
ID mid0; /* method's original id */ | |
struct RClass *class; /* receiver's class */ | |
struct RClass *origin; /* where method defined */ | |
NODE *method; | |
int noex; | |
}; | |
static struct cache_entry cache[CACHE_SIZE]; | |
void | |
rb_clear_cache() | |
{ | |
struct cache_entry *ent, *end; | |
ent = cache; end = ent + CACHE_SIZE; | |
while (ent < end) { | |
ent->mid = 0; | |
ent++; | |
} | |
} | |
void | |
rb_add_method(class, mid, node, noex) | |
struct RClass *class; | |
ID mid; | |
NODE *node; | |
int noex; | |
{ | |
NODE *body; | |
if (NIL_P(class)) class = (struct RClass*)cObject; | |
body = NEW_METHOD(node, noex); | |
st_insert(class->m_tbl, mid, body); | |
} | |
static NODE* | |
search_method(class, id, origin) | |
struct RClass *class, **origin; | |
ID id; | |
{ | |
NODE *body; | |
while (!st_lookup(class->m_tbl, id, &body)) { | |
class = class->super; | |
if (!class) return 0; | |
} | |
if (origin) *origin = class; | |
return body; | |
} | |
static NODE* | |
rb_get_method_body(classp, idp, noexp) | |
struct RClass **classp; | |
ID *idp; | |
int *noexp; | |
{ | |
ID id = *idp; | |
struct RClass *class = *classp; | |
NODE *body; | |
struct RClass *origin; | |
struct cache_entry *ent; | |
if ((body = search_method(class, id, &origin)) == 0) { | |
return 0; | |
} | |
if (!body->nd_body) return 0; | |
/* store in cache */ | |
ent = cache + EXPR1(class, id); | |
ent->class = class; | |
ent->noex = body->nd_noex; | |
body = body->nd_body; | |
if (nd_type(body) == NODE_FBODY) { | |
ent->mid = id; | |
*classp = ent->origin = (struct RClass*)body->nd_orig; | |
*idp = ent->mid0 = body->nd_mid; | |
body = ent->method = body->nd_head; | |
} | |
else { | |
*classp = ent->origin = origin; | |
ent->mid = ent->mid0 = id; | |
ent->method = body; | |
} | |
if (noexp) *noexp = ent->noex; | |
return body; | |
} | |
void | |
rb_alias(class, name, def) | |
struct RClass *class; | |
ID name, def; | |
{ | |
struct RClass *origin; | |
NODE *orig, *body; | |
if (name == def) return; | |
orig = search_method(class, def, &origin); | |
if (!orig || !orig->nd_body) { | |
if (TYPE(class) == T_MODULE) { | |
orig = search_method(cObject, def, &origin); | |
} | |
} | |
if (!orig || !orig->nd_body) { | |
NameError("undefined method `%s' for `%s'", | |
rb_id2name(def), rb_class2name((VALUE)class)); | |
} | |
body = orig->nd_body; | |
if (nd_type(body) == NODE_FBODY) { /* was alias */ | |
body = body->nd_head; | |
def = body->nd_mid; | |
origin = (struct RClass*)body->nd_orig; | |
} | |
st_insert(class->m_tbl, name, | |
NEW_METHOD(NEW_FBODY(body, def, origin), orig->nd_noex)); | |
} | |
static void | |
rb_export_method(class, name, noex) | |
struct RClass *class; | |
ID name; | |
int noex; | |
{ | |
NODE *body; | |
struct RClass *origin; | |
body = search_method(class, name, &origin); | |
if (!body && TYPE(class) == T_MODULE) { | |
body = search_method(cObject, name, &origin); | |
} | |
if (!body) { | |
NameError("undefined method `%s' for `%s'", | |
rb_id2name(name), rb_class2name((VALUE)class)); | |
} | |
if (body->nd_noex != noex) { | |
if (class == origin) { | |
body->nd_noex = noex; | |
} | |
else { | |
rb_clear_cache(); | |
rb_add_method(class, name, NEW_ZSUPER(), noex); | |
} | |
} | |
} | |
static VALUE | |
method_boundp(class, id, ex) | |
struct RClass *class; | |
ID id; | |
int ex; | |
{ | |
int noex; | |
if (rb_get_method_body(&class, &id, &noex)) { | |
if (ex && noex == NOEX_PRIVATE) | |
return FALSE; | |
return TRUE; | |
} | |
return FALSE; | |
} | |
int | |
rb_method_boundp(class, id, priv) | |
VALUE class; | |
ID id; | |
int priv; | |
{ | |
if (method_boundp(class, id, priv?NOEX_PRIVATE:NOEX_PUBLIC)) | |
return TRUE; | |
return FALSE; | |
} | |
static ID init, eqq, each, aref, aset; | |
VALUE errinfo = Qnil, errat = Qnil; | |
extern NODE *eval_tree; | |
extern int nerrs; | |
extern VALUE mKernel; | |
extern VALUE cModule; | |
extern VALUE cClass; | |
extern VALUE eFatal; | |
extern VALUE eGlobalExit; | |
extern VALUE eInterrupt; | |
extern VALUE eSystemExit; | |
extern VALUE eException; | |
extern VALUE eRuntimeError; | |
extern VALUE eSyntaxError; | |
static VALUE eLocalJumpError; | |
extern VALUE eSecurityError; | |
extern VALUE TopSelf; | |
struct FRAME *the_frame; | |
struct SCOPE *the_scope; | |
static struct FRAME *top_frame; | |
static struct SCOPE *top_scope; | |
#define PUSH_FRAME() { \ | |
struct FRAME _frame; \ | |
_frame.prev = the_frame; \ | |
_frame.file = sourcefile; \ | |
_frame.line = sourceline; \ | |
_frame.iter = the_iter->iter; \ | |
_frame.cbase = the_frame->cbase; \ | |
the_frame = &_frame; \ | |
#define POP_FRAME() the_frame = _frame.prev; } | |
struct BLOCK { | |
NODE *var; | |
NODE *body; | |
VALUE self; | |
struct FRAME frame; | |
struct SCOPE *scope; | |
struct RClass *class; | |
int level; | |
int iter; | |
struct RVarmap *d_vars; | |
#ifdef THREAD | |
VALUE orig_thread; | |
#endif | |
struct BLOCK *prev; | |
} *the_block; | |
#define PUSH_BLOCK(v,b) { \ | |
struct BLOCK _block; \ | |
_block.level = (int)prot_tag; \ | |
_block.var = v; \ | |
_block.body = b; \ | |
_block.self = self; \ | |
_block.frame = *the_frame; \ | |
_block.class = the_class; \ | |
_block.frame.file = sourcefile; \ | |
_block.frame.line = sourceline; \ | |
_block.scope = the_scope; \ | |
_block.d_vars = the_dyna_vars; \ | |
_block.prev = the_block; \ | |
_block.iter = the_iter->iter; \ | |
the_block = &_block; \ | |
#define PUSH_BLOCK2(b) { \ | |
struct BLOCK _block; \ | |
_block = *b; \ | |
_block.prev = the_block; \ | |
the_block = &_block; | |
#define POP_BLOCK() \ | |
the_block = the_block->prev; \ | |
} | |
struct RVarmap *the_dyna_vars; | |
#define PUSH_VARS() { \ | |
struct RVarmap *_old; \ | |
_old = the_dyna_vars; \ | |
the_dyna_vars = 0; | |
#define POP_VARS() \ | |
the_dyna_vars = _old; \ | |
} | |
VALUE | |
dyna_var_defined(id) | |
ID id; | |
{ | |
struct RVarmap *vars = the_dyna_vars; | |
while (vars) { | |
if (vars->id == id) return TRUE; | |
vars = vars->next; | |
} | |
return FALSE; | |
} | |
VALUE | |
dyna_var_ref(id) | |
ID id; | |
{ | |
struct RVarmap *vars = the_dyna_vars; | |
while (vars) { | |
if (vars->id == id) { | |
return vars->val; | |
} | |
vars = vars->next; | |
} | |
return Qnil; | |
} | |
VALUE | |
dyna_var_asgn(id, value) | |
ID id; | |
VALUE value; | |
{ | |
struct RVarmap *vars = the_dyna_vars; | |
while (vars) { | |
if (vars->id == id) { | |
vars->val = value; | |
return value; | |
} | |
vars = vars->next; | |
} | |
{ | |
NEWOBJ(_vars, struct RVarmap); | |
OBJSETUP(_vars, 0, T_VARMAP); | |
_vars->id = id; | |
_vars->val = value; | |
_vars->next = the_dyna_vars; | |
the_dyna_vars = _vars; | |
} | |
return value; | |
} | |
static struct iter { | |
int iter; | |
struct iter *prev; | |
} *the_iter; | |
#define ITER_NOT 0 | |
#define ITER_PRE 1 | |
#define ITER_CUR 2 | |
#define PUSH_ITER(i) { \ | |
struct iter _iter; \ | |
_iter.prev = the_iter; \ | |
_iter.iter = (i); \ | |
the_iter = &_iter; \ | |
#define POP_ITER() \ | |
the_iter = _iter.prev; \ | |
} | |
static struct tag { | |
jmp_buf buf; | |
struct FRAME *frame; | |
struct iter *iter; | |
struct tag *prev; | |
} *prot_tag; | |
#define PUSH_TAG() { \ | |
struct tag _tag; \ | |
_tag.frame = the_frame; \ | |
_tag.iter = the_iter; \ | |
_tag.prev = prot_tag; \ | |
prot_tag = &_tag; | |
#define EXEC_TAG() ((NODE*)setjmp(prot_tag->buf)) | |
#define JUMP_TAG(st) { \ | |
the_frame = prot_tag->frame; \ | |
the_iter = prot_tag->iter; \ | |
longjmp(prot_tag->buf,(int)(st)); \ | |
} | |
#define JUMP_TAG3(val,data1,data2) \ | |
JUMP_TAG(node_newnode(NODE_TAG,(val),(data1),(data2))) | |
#define JUMP_TAG2(val,data) JUMP_TAG3((val),(data),0) | |
#define POP_TAG() \ | |
prot_tag = _tag.prev; \ | |
} | |
#define TAG_RETURN 0x1 | |
#define TAG_BREAK 0x2 | |
#define TAG_NEXT 0x3 | |
#define TAG_RETRY 0x4 | |
#define TAG_REDO 0x5 | |
#define TAG_RAISE 0x6 | |
#define TAG_THROW 0x7 | |
#define TAG_FATAL 0x8 | |
#define IN_BLOCK 0x10 | |
struct RClass *the_class; | |
#define PUSH_CLASS() { \ | |
struct RClass *_class = the_class; \ | |
#define POP_CLASS() the_class = _class; } | |
#define PUSH_SCOPE() { \ | |
struct SCOPE *_old; \ | |
NEWOBJ(_scope, struct SCOPE); \ | |
OBJSETUP(_scope, 0, T_SCOPE); \ | |
_scope->local_tbl = 0; \ | |
_scope->local_vars = 0; \ | |
_scope->flag = 0; \ | |
_old = the_scope; \ | |
the_scope = _scope; \ | |
#define POP_SCOPE() \ | |
if (the_scope->flag == SCOPE_ALLOCA) {\ | |
the_scope->local_vars = 0;\ | |
the_scope->local_tbl = 0;\ | |
if (the_scope != top_scope)\ | |
gc_force_recycle(the_scope);\ | |
}\ | |
else {\ | |
the_scope->flag |= SCOPE_NOSTACK;\ | |
}\ | |
the_scope = _old;\ | |
} | |
static VALUE rb_eval(); | |
static VALUE eval(); | |
static NODE *compile(); | |
static VALUE rb_call(); | |
VALUE rb_apply(); | |
VALUE rb_funcall2(); | |
static VALUE module_setup(); | |
static VALUE massign(); | |
static void assign(); | |
static int safe_level = 0; | |
/* safe-level: | |
0 - strings from streams/environment/ARGV are tainted (default) | |
1 - no dangerous operation by tainted string | |
2 - some process operations prohibited | |
3 - all genetated strings are tainted | |
4 - no global variable value modification/no direct output | |
5 - no instance variable value modification | |
*/ | |
int | |
rb_safe_level() | |
{ | |
return safe_level; | |
} | |
void | |
rb_set_safe_level(level) | |
int level; | |
{ | |
if (level > safe_level) { | |
safe_level = level; | |
} | |
} | |
static VALUE | |
safe_getter() | |
{ | |
return INT2FIX(safe_level); | |
} | |
static void | |
safe_setter(val) | |
VALUE val; | |
{ | |
int level = NUM2INT(val); | |
if (level < safe_level) { | |
Raise(eSecurityError, "tried to downgrade safe level from %d to %d", | |
safe_level, level); | |
} | |
safe_level = level; | |
} | |
void | |
rb_check_safe_str(x) | |
VALUE x; | |
{ | |
if (TYPE(x)!= T_STRING) { | |
TypeError("wrong argument type %s (expected String)", | |
rb_class2name(CLASS_OF(x))); | |
} | |
if (rb_safe_level() > 0 && str_tainted(x)) { | |
Raise(eSecurityError, "Insecure operation - %s", | |
rb_id2name(the_frame->last_func)); | |
} | |
} | |
void | |
rb_secure(level) | |
int level; | |
{ | |
if (level <= safe_level) { | |
Raise(eSecurityError, "Insecure operation `%s' for level %d", | |
rb_id2name(the_frame->last_func), level); | |
} | |
} | |
extern int sourceline; | |
extern char *sourcefile; | |
static VALUE trace_func = 0; | |
static void call_trace_func(); | |
static void | |
error_pos() | |
{ | |
if (sourcefile) { | |
if (the_frame->last_func) { | |
fprintf(stderr, "%s:%d:in `%s'", sourcefile, sourceline, | |
rb_id2name(the_frame->last_func)); | |
} | |
else { | |
fprintf(stderr, "%s:%d", sourcefile, sourceline); | |
} | |
} | |
} | |
static void | |
error_print() | |
{ | |
VALUE eclass; | |
if (NIL_P(errinfo)) return; | |
if (!NIL_P(errat)) { | |
VALUE mesg = Qnil; | |
switch (TYPE(errat)) { | |
case T_STRING: | |
mesg = errat; | |
errat = Qnil; | |
break; | |
case T_ARRAY: | |
mesg = RARRAY(errat)->ptr[0]; | |
break; | |
} | |
if (NIL_P(mesg)) error_pos(); | |
else { | |
fwrite(RSTRING(mesg)->ptr, 1, RSTRING(mesg)->len, stderr); | |
} | |
} | |
eclass = CLASS_OF(errinfo); | |
if (eclass == eRuntimeError && RSTRING(errinfo)->len == 0) { | |
fprintf(stderr, ": unhandled exception\n"); | |
} | |
else { | |
PUSH_TAG(); | |
if (EXEC_TAG() == 0) { | |
VALUE epath = rb_class_path(eclass); | |
if (RSTRING(epath)->ptr[0] != '#') { | |
fprintf(stderr, ": "); | |
fwrite(RSTRING(epath)->ptr, 1, RSTRING(epath)->len, stderr); | |
} | |
} | |
POP_TAG(); | |
if (RSTRING(errinfo)->len > 0) { | |
fprintf(stderr, ": "); | |
fwrite(RSTRING(errinfo)->ptr, 1, RSTRING(errinfo)->len, stderr); | |
} | |
if (RSTRING(errinfo)->ptr[RSTRING(errinfo)->len - 1] != '\n') { | |
putc('\n', stderr); | |
} | |
} | |
if (!NIL_P(errat)) { | |
int i; | |
struct RArray *ep = RARRAY(errat); | |
#define TRACE_MAX (TRACE_HEAD+TRACE_TAIL+5) | |
#define TRACE_HEAD 8 | |
#define TRACE_TAIL 5 | |
for (i=1; i<ep->len; i++) { | |
fprintf(stderr, "\tfrom %s\n", RSTRING(ep->ptr[i])->ptr); | |
if (i == TRACE_HEAD && ep->len > TRACE_MAX) { | |
fprintf(stderr, "\t ... %d levels...\n", | |
ep->len - TRACE_HEAD - TRACE_TAIL); | |
i = ep->len - TRACE_TAIL; | |
} | |
} | |
} | |
} | |
#ifndef NT | |
extern char **environ; | |
#endif | |
char **origenviron; | |
void | |
ruby_init() | |
{ | |
static struct FRAME frame; | |
static struct iter iter; | |
NODE *state; | |
the_frame = top_frame = &frame; | |
the_iter = &iter; | |
origenviron = environ; | |
init_heap(); | |
PUSH_SCOPE(); | |
the_scope->local_vars = 0; | |
the_scope->local_tbl = 0; | |
top_scope = the_scope; | |
PUSH_TAG() | |
if ((state = EXEC_TAG()) == 0) { | |
rb_call_inits(); | |
the_class = (struct RClass*)cObject; | |
the_frame->cbase = (VALUE)node_newnode(NODE_CREF,cObject,0,0); | |
rb_define_global_const("TOPLEVEL_BINDING", f_binding(TopSelf)); | |
ruby_prog_init(); | |
} | |
POP_TAG(); | |
if (state) error_print(); | |
POP_SCOPE(); | |
the_scope = top_scope; | |
} | |
static int ext_init = 0; | |
void | |
ruby_options(argc, argv) | |
int argc; | |
char **argv; | |
{ | |
NODE *state; | |
PUSH_TAG() | |
if ((state = EXEC_TAG()) == 0) { | |
NODE *save; | |
Init_ext(); | |
ext_init = 1; | |
ruby_process_options(argc, argv); | |
save = eval_tree; | |
rb_require_modules(); | |
eval_tree = save; | |
} | |
POP_TAG(); | |
if (state) { | |
error_print(); | |
exit(1); | |
} | |
} | |
static VALUE | |
eval_node(self) | |
VALUE self; | |
{ | |
VALUE result = Qnil; | |
NODE *tree; | |
if (!eval_tree) return Qnil; | |
tree = eval_tree; | |
eval_tree = 0; | |
result = rb_eval(self, tree); | |
return result; | |
} | |
int rb_in_eval; | |
#ifdef THREAD | |
static void thread_cleanup(); | |
static void thread_wait_other_threads(); | |
static VALUE thread_current(); | |
#endif | |
static int exit_status; | |
void | |
ruby_run() | |
{ | |
NODE *state; | |
static NODE *ex; | |
if (nerrs > 0) exit(nerrs); | |
init_stack(); | |
errat = Qnil; /* clear for execution */ | |
PUSH_TAG(); | |
PUSH_ITER(ITER_NOT); | |
if ((state = EXEC_TAG()) == 0) { | |
if (!ext_init) Init_ext(); | |
eval_node(TopSelf); | |
} | |
POP_ITER(); | |
POP_TAG(); | |
if (state && !ex) ex = state; | |
PUSH_TAG(); | |
PUSH_ITER(ITER_NOT); | |
if ((state = EXEC_TAG()) == 0) { | |
rb_trap_exit(); | |
#ifdef THREAD | |
thread_cleanup(); | |
thread_wait_other_threads(); | |
#endif | |
} | |
else { | |
ex = state; | |
} | |
POP_ITER(); | |
POP_TAG(); | |
if (!ex) { | |
exit(0); | |
} | |
switch (ex->nd_tag) { | |
case IN_BLOCK|TAG_RETURN: | |
case TAG_RETURN: | |
error_pos(); | |
fprintf(stderr, "unexpected return\n"); | |
exit(1); | |
break; | |
case TAG_NEXT: | |
error_pos(); | |
fprintf(stderr, "unexpected next\n"); | |
exit(1); | |
break; | |
case IN_BLOCK|TAG_BREAK: | |
case TAG_BREAK: | |
error_pos(); | |
fprintf(stderr, "unexpected break\n"); | |
exit(1); | |
break; | |
case TAG_REDO: | |
error_pos(); | |
fprintf(stderr, "unexpected redo\n"); | |
exit(1); | |
break; | |
case TAG_RETRY: | |
error_pos(); | |
fprintf(stderr, "retry outside of rescue clause\n"); | |
exit(1); | |
break; | |
case TAG_RAISE: | |
case TAG_FATAL: | |
if (obj_is_kind_of(errinfo, eSystemExit)) { | |
exit(exit_status); | |
} | |
error_print(); | |
exit(1); | |
break; | |
case TAG_THROW: | |
error_pos(); | |
fprintf(stderr, "uncaught throw `%s'\n", rb_id2name(ex->nd_tlev)); | |
exit(1); | |
break; | |
default: | |
Bug("Unknown longjmp status %d", ex->nd_tag); | |
break; | |
} | |
} | |
static void | |
compile_error(at) | |
char *at; | |
{ | |
VALUE mesg; | |
mesg = errinfo; | |
nerrs = 0; | |
errinfo = exc_new2(eSyntaxError, "compile error in "); | |
str_cat(errinfo, at, strlen(at)); | |
str_cat(errinfo, ":\n", 2); | |
str_cat(errinfo, RSTRING(mesg)->ptr, RSTRING(mesg)->len); | |
rb_raise(errinfo); | |
} | |
VALUE | |
rb_eval_string(str) | |
char *str; | |
{ | |
VALUE v; | |
char *oldsrc = sourcefile; | |
sourcefile = "(eval)"; | |
v = eval(TopSelf, str_new2(str), Qnil); | |
sourcefile = oldsrc; | |
return v; | |
} | |
void | |
rb_eval_cmd(cmd, arg) | |
VALUE cmd, arg; | |
{ | |
NODE *state; | |
struct SCOPE *saved_scope; | |
volatile int safe = rb_safe_level(); | |
if (TYPE(cmd) != T_STRING) { | |
if (obj_is_kind_of(cmd, cProc)) { | |
proc_call(cmd, arg); | |
return; | |
} | |
} | |
PUSH_CLASS(); | |
PUSH_TAG(); | |
saved_scope = the_scope; | |
the_scope = top_scope; | |
the_class = (struct RClass*)cObject; | |
if (str_tainted(cmd)) { | |
safe_level = 5; | |
} | |
if ((state = EXEC_TAG()) == 0) { | |
eval(TopSelf, cmd, Qnil); | |
} | |
the_scope = saved_scope; | |
safe_level = safe; | |
POP_TAG(); | |
POP_CLASS(); | |
if (state == 0) return; | |
switch (state->nd_tag) { | |
case TAG_RETURN: | |
Raise(eLocalJumpError, "unexpected return"); | |
break; | |
case TAG_NEXT: | |
Raise(eLocalJumpError, "unexpected next"); | |
break; | |
case TAG_BREAK: | |
Raise(eLocalJumpError, "unexpected break"); | |
break; | |
case TAG_REDO: | |
Raise(eLocalJumpError, "unexpected redo"); | |
break; | |
case TAG_RETRY: | |
Raise(eLocalJumpError, "retry outside of rescue clause"); | |
break; | |
default: | |
JUMP_TAG(state); | |
break; | |
} | |
} | |
void | |
rb_trap_eval(cmd, sig) | |
VALUE cmd; | |
int sig; | |
{ | |
NODE *state; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
rb_eval_cmd(cmd, ary_new3(1, INT2FIX(sig))); | |
} | |
POP_TAG(); | |
if (state) { | |
trap_immediate = 0; | |
JUMP_TAG(state); | |
} | |
} | |
static VALUE | |
superclass(self, node) | |
VALUE self; | |
NODE *node; | |
{ | |
VALUE val = 0; | |
NODE *state; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
val = rb_eval(self, node); | |
} | |
POP_TAG(); | |
if (state) { | |
if (state->nd_tag == TAG_RAISE) { | |
superclass_error: | |
switch (nd_type(node)) { | |
case NODE_COLON2: | |
TypeError("undefined superclass `%s'", rb_id2name(node->nd_mid)); | |
case NODE_CVAR: | |
TypeError("undefined superclass `%s'", rb_id2name(node->nd_vid)); | |
default: | |
TypeError("superclass undefined"); | |
} | |
} | |
JUMP_TAG(state); | |
} | |
if (TYPE(val) != T_CLASS) goto superclass_error; | |
if (FL_TEST(val, FL_SINGLETON)) { | |
TypeError("can't make subclass of virtual class"); | |
} | |
return val; | |
} | |
static VALUE | |
ev_const_defined(cref, id) | |
NODE *cref; | |
ID id; | |
{ | |
NODE *cbase = cref; | |
while (cbase && cbase->nd_clss != cObject) { | |
struct RClass *class = RCLASS(cbase->nd_clss); | |
if (class->iv_tbl && | |
st_lookup(class->iv_tbl, id, 0)) { | |
return TRUE; | |
} | |
cbase = cbase->nd_next; | |
} | |
return rb_const_defined(cref->nd_clss, id); | |
} | |
static VALUE | |
ev_const_get(cref, id) | |
NODE *cref; | |
ID id; | |
{ | |
NODE *cbase = cref; | |
VALUE result; | |
while (cbase && cbase->nd_clss != cObject) { | |
struct RClass *class = RCLASS(cbase->nd_clss); | |
if (class->iv_tbl && | |
st_lookup(class->iv_tbl, id, &result)) { | |
return result; | |
} | |
cbase = cbase->nd_next; | |
} | |
return rb_const_get(cref->nd_clss, id); | |
} | |
#define SETUP_ARGS(anode) {\ | |
NODE *n = anode;\ | |
if (!n) {\ | |
argc = 0;\ | |
argv = 0;\ | |
}\ | |
else if (nd_type(n) == NODE_ARRAY) {\ | |
argc=n->nd_alen;\ | |
if (argc > 0) {\ | |
int i;\ | |
int line = sourceline;\ | |
n = anode;\ | |
argv = ALLOCA_N(VALUE,argc);\ | |
for (i=0;i<argc;i++) {\ | |
argv[i] = rb_eval(self,n->nd_head);\ | |
n=n->nd_next;\ | |
}\ | |
sourcefile = anode->file;\ | |
sourceline = line;\ | |
}\ | |
else {\ | |
argc = 0;\ | |
argv = 0;\ | |
}\ | |
}\ | |
else {\ | |
VALUE args = rb_eval(self,n);\ | |
int line = sourceline;\ | |
if (TYPE(args) != T_ARRAY)\ | |
args = rb_to_a(args);\ | |
argc = RARRAY(args)->len;\ | |
argv = ALLOCA_N(VALUE, argc);\ | |
MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc);\ | |
sourcefile = anode->file;\ | |
sourceline = line;\ | |
}\ | |
} | |
int | |
rb_test_false_or_nil(v) | |
VALUE v; | |
{ | |
return (v != Qnil) && (v != FALSE); | |
} | |
#define MATCH_DATA the_scope->local_vars[node->nd_cnt] | |
static char* | |
is_defined(self, node, buf) | |
VALUE self; | |
NODE *node; /* OK */ | |
char *buf; | |
{ | |
VALUE val; /* OK */ | |
NODE *state; | |
node = node->nd_head; | |
switch (nd_type(node)) { | |
case NODE_SUPER: | |
case NODE_ZSUPER: | |
if (the_frame->last_func == 0) return 0; | |
else if (method_boundp(the_frame->last_class->super, | |
the_frame->last_func, 1)) { | |
return "super"; | |
} | |
break; | |
case NODE_FCALL: | |
case NODE_VCALL: | |
val = CLASS_OF(self); | |
goto check_bound; | |
case NODE_CALL: | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
val = rb_eval(self, node->nd_recv); | |
val = CLASS_OF(val); | |
} | |
POP_TAG(); | |
if (state) { | |
return 0; | |
} | |
check_bound: | |
if (method_boundp(val, node->nd_mid, | |
nd_type(node)== NODE_CALL)) { | |
return "method"; | |
} | |
break; | |
case NODE_YIELD: | |
if (iterator_p()) { | |
return "yield"; | |
} | |
break; | |
case NODE_SELF: | |
return "self"; | |
case NODE_NIL: | |
return "nil"; | |
case NODE_ATTRSET: | |
case NODE_OP_ASGN1: | |
case NODE_OP_ASGN2: | |
case NODE_MASGN: | |
case NODE_LASGN: | |
case NODE_DASGN: | |
case NODE_GASGN: | |
case NODE_IASGN: | |
case NODE_CASGN: | |
return "assignment"; | |
case NODE_LVAR: | |
case NODE_DVAR: | |
return "local-variable"; | |
case NODE_GVAR: | |
if (rb_gvar_defined(node->nd_entry)) { | |
return "global-variable"; | |
} | |
break; | |
case NODE_IVAR: | |
if (rb_ivar_defined(self, node->nd_vid)) { | |
return "instance-variable"; | |
} | |
break; | |
case NODE_CVAR: | |
if (ev_const_defined(the_frame->cbase, node->nd_vid)) { | |
return "constant"; | |
} | |
break; | |
case NODE_COLON2: | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
val = rb_eval(self, node->nd_head); | |
} | |
POP_TAG(); | |
if (state) { | |
return 0; | |
} | |
else { | |
switch (TYPE(val)) { | |
case T_CLASS: | |
case T_MODULE: | |
if (rb_const_defined_at(val, node->nd_mid)) | |
return "constant"; | |
} | |
} | |
break; | |
case NODE_NTH_REF: | |
if (reg_nth_defined(node->nd_nth, MATCH_DATA)) { | |
sprintf(buf, "$%d", node->nd_nth); | |
return buf; | |
} | |
break; | |
case NODE_BACK_REF: | |
if (reg_nth_defined(0, MATCH_DATA)) { | |
sprintf(buf, "$%c", node->nd_nth); | |
return buf; | |
} | |
break; | |
default: | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
rb_eval(self, node); | |
} | |
POP_TAG(); | |
if (!state) { | |
return "expression"; | |
} | |
break; | |
} | |
return 0; | |
} | |
static int handle_rescue(); | |
VALUE rb_yield_0(); | |
static void blk_free(); | |
static VALUE | |
set_trace_func(obj, trace) | |
VALUE obj; | |
struct RData *trace; | |
{ | |
if (NIL_P(trace)) { | |
trace_func = 0; | |
return Qnil; | |
} | |
if (TYPE(trace) != T_DATA || trace->dfree != blk_free) { | |
TypeError("trace_func needs to be Proc"); | |
} | |
return trace_func = (VALUE)trace; | |
} | |
static void | |
call_trace_func(event, file, line, self, id) | |
char *event; | |
char *file; | |
int line; | |
VALUE self; | |
ID id; | |
{ | |
NODE *state; | |
volatile VALUE trace; | |
struct FRAME *prev; | |
if (!trace_func) return; | |
trace = trace_func; | |
trace_func = 0; | |
#ifdef THREAD | |
thread_critical++; | |
#endif | |
prev = the_frame; | |
PUSH_FRAME(); | |
*the_frame = *_frame.prev; | |
the_frame->prev = prev; | |
the_frame->line = sourceline = line; | |
the_frame->file = sourcefile = file; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
proc_call(trace, ary_new3(5, str_new2(event), | |
str_new2(sourcefile), | |
INT2FIX(sourceline), | |
INT2FIX(id), | |
self?f_binding(self):Qnil)); | |
} | |
POP_TAG(); | |
POP_FRAME(); | |
#ifdef THREAD | |
thread_critical--; | |
#endif | |
if (!trace_func) trace_func = trace; | |
if (state) JUMP_TAG(state); | |
} | |
static VALUE | |
rb_eval(self, node) | |
VALUE self; | |
NODE * volatile node; | |
{ | |
NODE *state; | |
volatile VALUE result = Qnil; | |
#define RETURN(v) { result = (v); goto finish; } | |
again: | |
if (!node) RETURN(Qnil); | |
#if 0 | |
sourceline = nd_line(node); | |
sourcefile = node->file; | |
#endif | |
switch (nd_type(node)) { | |
case NODE_BLOCK: | |
while (node) { | |
result = rb_eval(self, node->nd_head); | |
node = node->nd_next; | |
} | |
break; | |
/* begin .. end without clauses */ | |
case NODE_BEGIN: | |
node = node->nd_body; | |
goto again; | |
/* nodes for speed-up(default match) */ | |
case NODE_MATCH: | |
result = reg_match2(node->nd_head->nd_lit); | |
break; | |
/* nodes for speed-up(top-level loop for -n/-p) */ | |
case NODE_OPT_N: | |
while (!NIL_P(f_gets())) { | |
rb_eval(self, node->nd_body); | |
} | |
RETURN(Qnil); | |
case NODE_SELF: | |
RETURN(self); | |
case NODE_NIL: | |
RETURN(Qnil); | |
case NODE_IF: | |
if (RTEST(rb_eval(self, node->nd_cond))) { | |
node = node->nd_body; | |
} | |
else { | |
node = node->nd_else; | |
} | |
goto again; | |
case NODE_CASE: | |
{ | |
VALUE val; | |
val = rb_eval(self, node->nd_head); | |
node = node->nd_body; | |
while (node) { | |
NODE *tag; | |
if (nd_type(node) != NODE_WHEN) { | |
goto again; | |
} | |
tag = node->nd_head; | |
while (tag) { | |
if (trace_func) { | |
call_trace_func("line", tag->file, nd_line(tag), | |
self, the_frame->last_func); | |
} | |
if (RTEST(rb_funcall2(rb_eval(self, tag->nd_head),eqq,1,&val))){ | |
node = node->nd_body; | |
goto again; | |
} | |
tag = tag->nd_next; | |
} | |
node = node->nd_next; | |
} | |
} | |
RETURN(Qnil); | |
case NODE_WHILE: | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
if (node->nd_state && !RTEST(rb_eval(self, node->nd_cond))) | |
goto while_out; | |
do { | |
while_redo: | |
rb_eval(self, node->nd_body); | |
while_next: | |
; | |
} while (RTEST(rb_eval(self, node->nd_cond))); | |
} | |
else { | |
switch (state->nd_tag) { | |
case TAG_REDO: | |
state = 0; | |
goto while_redo; | |
case TAG_NEXT: | |
state = 0; | |
goto while_next; | |
case TAG_BREAK: | |
state = 0; | |
default: | |
break; | |
} | |
} | |
while_out: | |
POP_TAG(); | |
if (state) { | |
JUMP_TAG(state); | |
} | |
RETURN(Qnil); | |
case NODE_UNTIL: | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
if (node->nd_state && RTEST(rb_eval(self, node->nd_cond))) | |
goto until_out; | |
do { | |
until_redo: | |
rb_eval(self, node->nd_body); | |
until_next: | |
; | |
} while (!RTEST(rb_eval(self, node->nd_cond))); | |
} | |
else { | |
switch (state->nd_tag) { | |
case TAG_REDO: | |
state = 0; | |
goto until_redo; | |
case TAG_NEXT: | |
state = 0; | |
goto until_next; | |
case TAG_BREAK: | |
state = 0; | |
default: | |
break; | |
} | |
} | |
until_out: | |
POP_TAG(); | |
if (state) { | |
JUMP_TAG(state); | |
} | |
RETURN(Qnil); | |
case NODE_ITER: | |
case NODE_FOR: | |
{ | |
int tag_level; | |
iter_retry: | |
PUSH_BLOCK(node->nd_var, node->nd_body); | |
PUSH_TAG(); | |
state = EXEC_TAG(); | |
if (state == 0) { | |
if (nd_type(node) == NODE_ITER) { | |
PUSH_ITER(ITER_PRE); | |
result = rb_eval(self, node->nd_iter); | |
POP_ITER(); | |
} | |
else { | |
VALUE recv; | |
int line = sourceline; | |
recv = rb_eval(self, node->nd_iter); | |
PUSH_ITER(ITER_PRE); | |
sourcefile = node->file; | |
sourceline = line; | |
result = rb_call(CLASS_OF(recv),recv,each,0,0,0); | |
POP_ITER(); | |
} | |
} | |
POP_TAG(); | |
tag_level = the_block->level; | |
POP_BLOCK(); | |
if (state == 0) break; | |
switch (state->nd_tag) { | |
case TAG_RETRY: | |
goto iter_retry; | |
case IN_BLOCK|TAG_BREAK: | |
if (state->nd_tlev != tag_level) { | |
JUMP_TAG(state); | |
} | |
result = Qnil; | |
break; | |
case IN_BLOCK|TAG_RETURN: | |
if (state->nd_tlev == tag_level) { | |
state->nd_tag &= ~IN_BLOCK; | |
} | |
/* fall through */ | |
default: | |
JUMP_TAG(state); | |
} | |
} | |
break; | |
case NODE_YIELD: | |
result = rb_yield_0(rb_eval(self, node->nd_stts), 0); | |
break; | |
case NODE_RESCUE: | |
retry_entry: | |
{ | |
volatile VALUE e_info = errinfo, e_at = errat; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
result = rb_eval(self, node->nd_head); | |
} | |
POP_TAG(); | |
if (state) { | |
if (state->nd_tag == TAG_RAISE) { | |
NODE * volatile resq = node->nd_resq; | |
while (resq) { | |
if (handle_rescue(self, resq)) { | |
state = 0; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
result = rb_eval(self, resq->nd_body); | |
} | |
POP_TAG(); | |
if (state == 0) { | |
errinfo = e_info; | |
errat = e_at; | |
} | |
else if (state->nd_tag == TAG_RETRY) { | |
state = 0; | |
goto retry_entry; | |
} | |
break; | |
} | |
resq = resq->nd_head; /* next rescue */ | |
} | |
} | |
if (state) { | |
JUMP_TAG(state); | |
} | |
} | |
} | |
break; | |
case NODE_ENSURE: | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
result = rb_eval(self, node->nd_head); | |
} | |
POP_TAG(); | |
rb_eval(self, node->nd_ensr); | |
if (state) { | |
JUMP_TAG(state); | |
} | |
break; | |
case NODE_AND: | |
result = rb_eval(self, node->nd_1st); | |
if (!RTEST(result)) break; | |
node = node->nd_2nd; | |
goto again; | |
case NODE_OR: | |
result = rb_eval(self, node->nd_1st); | |
if (RTEST(result)) break; | |
node = node->nd_2nd; | |
goto again; | |
case NODE_NOT: | |
if (RTEST(rb_eval(self, node->nd_body))) result = FALSE; | |
else result = TRUE; | |
break; | |
case NODE_DOT2: | |
case NODE_DOT3: | |
RETURN(range_new(rb_eval(self, node->nd_beg), rb_eval(self, node->nd_end))); | |
case NODE_FLIP2: /* like AWK */ | |
if (node->nd_state == 0) { | |
if (RTEST(rb_eval(self, node->nd_beg))) { | |
node->nd_state = rb_eval(self, node->nd_end)?0:1; | |
result = TRUE; | |
} | |
else { | |
result = FALSE; | |
} | |
} | |
else { | |
if (RTEST(rb_eval(self, node->nd_end))) { | |
node->nd_state = 0; | |
} | |
result = TRUE; | |
} | |
break; | |
case NODE_FLIP3: /* like SED */ | |
if (node->nd_state == 0) { | |
if (RTEST(rb_eval(self, node->nd_beg))) { | |
node->nd_state = 1; | |
result = TRUE; | |
} | |
result = FALSE; | |
} | |
else { | |
if (RTEST(rb_eval(self, node->nd_end))) { | |
node->nd_state = 0; | |
} | |
result = TRUE; | |
} | |
break; | |
case NODE_RETURN: | |
JUMP_TAG2(TAG_RETURN,(node->nd_stts)?rb_eval(self, node->nd_stts):Qnil); | |
break; | |
case NODE_CALL: | |
{ | |
VALUE recv; | |
int argc; VALUE *argv; /* used in SETUP_ARGS */ | |
PUSH_ITER(ITER_NOT); | |
recv = rb_eval(self, node->nd_recv); | |
SETUP_ARGS(node->nd_args); | |
POP_ITER(); | |
result = rb_call(CLASS_OF(recv),recv,node->nd_mid,argc,argv,0); | |
} | |
break; | |
case NODE_FCALL: | |
{ | |
int argc; VALUE *argv; /* used in SETUP_ARGS */ | |
PUSH_ITER(ITER_NOT); | |
SETUP_ARGS(node->nd_args); | |
POP_ITER(); | |
result = rb_call(CLASS_OF(self),self,node->nd_mid,argc,argv,1); | |
} | |
break; | |
case NODE_VCALL: | |
result = rb_call(CLASS_OF(self),self,node->nd_mid,0,0,2); | |
break; | |
case NODE_SUPER: | |
case NODE_ZSUPER: | |
{ | |
int argc; VALUE *argv; /* used in SETUP_ARGS */ | |
if (nd_type(node) == NODE_ZSUPER) { | |
argc = the_frame->argc; | |
argv = the_frame->argv; | |
} | |
else { | |
PUSH_ITER(ITER_NOT); | |
SETUP_ARGS(node->nd_args); | |
POP_ITER(); | |
} | |
PUSH_ITER(the_iter->iter?ITER_PRE:ITER_NOT); | |
result = rb_call(the_frame->last_class->super, self, | |
the_frame->last_func, argc, argv, 1); | |
POP_ITER(); | |
} | |
break; | |
case NODE_SCOPE: | |
{ | |
VALUE save = the_frame->cbase; | |
PUSH_SCOPE(); | |
PUSH_TAG(); | |
if (node->nd_rval) the_frame->cbase = (VALUE)node->nd_rval; | |
if (node->nd_tbl) { | |
VALUE *vars = ALLOCA_N(VALUE, node->nd_tbl[0]+1); | |
*vars++ = (VALUE)node; | |
the_scope->local_vars = vars; | |
memclear(the_scope->local_vars, node->nd_tbl[0]); | |
the_scope->local_tbl = node->nd_tbl; | |
} | |
else { | |
the_scope->local_vars = 0; | |
the_scope->local_tbl = 0; | |
} | |
if ((state = EXEC_TAG()) == 0) { | |
result = rb_eval(self, node->nd_body); | |
} | |
POP_TAG(); | |
POP_SCOPE(); | |
the_frame->cbase = save; | |
if (state) JUMP_TAG(state); | |
} | |
break; | |
case NODE_OP_ASGN1: | |
{ | |
int argc; VALUE *argv; /* used in SETUP_ARGS */ | |
VALUE recv, val; | |
NODE *rval; | |
recv = rb_eval(self, node->nd_recv); | |
rval = node->nd_args->nd_head; | |
SETUP_ARGS(node->nd_args->nd_next); | |
val = rb_funcall2(recv, aref, argc-1, argv); | |
val = rb_funcall(val, node->nd_mid, 1, rb_eval(self, rval)); | |
argv[argc-1] = val; | |
val = rb_funcall2(recv, aset, argc, argv); | |
result = val; | |
} | |
break; | |
case NODE_OP_ASGN2: | |
{ | |
ID id = node->nd_next->nd_vid; | |
VALUE recv, val; | |
recv = rb_eval(self, node->nd_recv); | |
val = rb_funcall(recv, id, 0); | |
val = rb_funcall(val, node->nd_next->nd_mid, 1, | |
rb_eval(self, node->nd_value)); | |
rb_funcall2(recv, id_attrset(id), 1, &val); | |
result = val; | |
} | |
break; | |
case NODE_MASGN: | |
result = massign(self, node, rb_eval(self, node->nd_value)); | |
break; | |
case NODE_LASGN: | |
if (the_scope->local_vars == 0) | |
Bug("unexpected local variable assignment"); | |
the_scope->local_vars[node->nd_cnt] = rb_eval(self, node->nd_value); | |
result = the_scope->local_vars[node->nd_cnt]; | |
break; | |
case NODE_DASGN: | |
result = dyna_var_asgn(node->nd_vid, rb_eval(self, node->nd_value)); | |
break; | |
case NODE_GASGN: | |
{ | |
VALUE val; | |
val = rb_eval(self, node->nd_value); | |
rb_gvar_set(node->nd_entry, val); | |
result = val; | |
} | |
break; | |
case NODE_IASGN: | |
{ | |
VALUE val; | |
val = rb_eval(self, node->nd_value); | |
rb_ivar_set(self, node->nd_vid, val); | |
result = val; | |
} | |
break; | |
case NODE_CASGN: | |
{ | |
VALUE val; | |
val = rb_eval(self, node->nd_value); | |
/* check for static scope constants */ | |
if (verbose && ev_const_defined(the_frame->cbase, node->nd_vid)) { | |
Warning("already initialized constant %s", | |
rb_id2name(node->nd_vid)); | |
} | |
rb_const_set(the_class, node->nd_vid, val); | |
result = val; | |
} | |
break; | |
case NODE_LVAR: | |
if (the_scope->local_vars == 0) { | |
Bug("unexpected local variable"); | |
} | |
result = the_scope->local_vars[node->nd_cnt]; | |
break; | |
case NODE_DVAR: | |
result = dyna_var_ref(node->nd_vid); | |
break; | |
case NODE_GVAR: | |
result = rb_gvar_get(node->nd_entry); | |
break; | |
case NODE_IVAR: | |
result = rb_ivar_get(self, node->nd_vid); | |
break; | |
case NODE_CVAR: | |
result = ev_const_get(the_frame->cbase, node->nd_vid); | |
break; | |
case NODE_COLON2: | |
{ | |
VALUE cls; | |
cls = rb_eval(self, node->nd_head); | |
switch (TYPE(cls)) { | |
case T_CLASS: | |
case T_MODULE: | |
break; | |
default: | |
Check_Type(cls, T_CLASS); | |
break; | |
} | |
result = rb_const_get_at(cls, node->nd_mid); | |
} | |
break; | |
case NODE_NTH_REF: | |
result = reg_nth_match(node->nd_nth, MATCH_DATA); | |
break; | |
case NODE_BACK_REF: | |
switch (node->nd_nth) { | |
case '&': | |
result = reg_last_match(MATCH_DATA); | |
break; | |
case '`': | |
result = reg_match_pre(MATCH_DATA); | |
break; | |
case '\'': | |
result = reg_match_post(MATCH_DATA); | |
break; | |
case '+': | |
result = reg_match_last(MATCH_DATA); | |
break; | |
default: | |
Bug("unexpected back-ref"); | |
} | |
break; | |
case NODE_HASH: | |
{ | |
NODE *list; | |
VALUE hash = hash_new(); | |
VALUE key, val; | |
list = node->nd_head; | |
while (list) { | |
key = rb_eval(self, list->nd_head); | |
list = list->nd_next; | |
if (list == 0) | |
Bug("odd number list for Hash"); | |
val = rb_eval(self, list->nd_head); | |
list = list->nd_next; | |
hash_aset(hash, key, val); | |
} | |
result = hash; | |
} | |
break; | |
case NODE_ZARRAY: /* zero length list */ | |
result = ary_new(); | |
break; | |
case NODE_ARRAY: | |
{ | |
VALUE ary; | |
int i; | |
i = node->nd_alen; | |
ary = ary_new2(i); | |
for (i=0;node;node=node->nd_next) { | |
RARRAY(ary)->ptr[i++] = rb_eval(self, node->nd_head); | |
RARRAY(ary)->len = i; | |
} | |
result = ary; | |
} | |
break; | |
case NODE_STR: | |
result = str_new3(node->nd_lit); | |
break; | |
case NODE_DSTR: | |
case NODE_DXSTR: | |
case NODE_DREGX: | |
case NODE_DREGX_ONCE: | |
{ | |
VALUE str, str2; | |
NODE *list = node->nd_next; | |
str = str_new3(node->nd_lit); | |
while (list) { | |
if (nd_type(list->nd_head) == NODE_STR) { | |
str2 = list->nd_head->nd_lit; | |
} | |
else { | |
if (nd_type(list->nd_head) == NODE_EVSTR) { | |
rb_in_eval++; | |
list->nd_head = compile(list->nd_head->nd_lit); | |
rb_in_eval--; | |
if (nerrs > 0) { | |
compile_error("string expand"); | |
} | |
} | |
str2 = rb_eval(self, list->nd_head); | |
str2 = obj_as_string(str2); | |
} | |
if (str2) { | |
str_cat(str, RSTRING(str2)->ptr, RSTRING(str2)->len); | |
} | |
list = list->nd_next; | |
} | |
switch (nd_type(node)) { | |
case NODE_DREGX: | |
result = reg_new(RSTRING(str)->ptr, RSTRING(str)->len, | |
node->nd_cflag); | |
break; | |
case NODE_DREGX_ONCE: /* regexp expand once */ | |
result = reg_new(RSTRING(str)->ptr, RSTRING(str)->len, | |
node->nd_cflag); | |
nd_set_type(node, NODE_LIT); | |
node->nd_lit = result; | |
break; | |
case NODE_DXSTR: | |
result = rb_funcall(self, '`', 1, str); | |
break; | |
default: | |
result = str; | |
break; | |
} | |
} | |
break; | |
case NODE_XSTR: | |
result = rb_funcall(self, '`', 1, node->nd_lit); | |
break; | |
case NODE_LIT: | |
result = node->nd_lit; | |
break; | |
case NODE_ATTRSET: | |
if (the_frame->argc != 1) | |
ArgError("Wrong # of arguments(%d for 1)", the_frame->argc); | |
result = rb_ivar_set(self, node->nd_vid, the_frame->argv[0]); | |
break; | |
case NODE_DEFN: | |
if (node->nd_defn) { | |
NODE *body; | |
VALUE origin; | |
int noex; | |
body = search_method(the_class, node->nd_mid, &origin); | |
if (body) { | |
if (origin == (VALUE)the_class) { | |
Warning("redefine %s", rb_id2name(node->nd_mid)); | |
} | |
rb_clear_cache(); | |
} | |
if (body) noex = body->nd_noex; | |
else noex = node->nd_noex; /* default(1 for toplevel) */ | |
rb_add_method(the_class, node->nd_mid, node->nd_defn, noex); | |
result = Qnil; | |
} | |
break; | |
case NODE_DEFS: | |
if (node->nd_defn) { | |
VALUE recv = rb_eval(self, node->nd_recv); | |
VALUE class; | |
NODE *body; | |
if (FIXNUM_P(recv)) { | |
TypeError("Can't define method \"%s\" for Fixnum", | |
rb_id2name(node->nd_mid)); | |
} | |
if (NIL_P(recv)) { | |
TypeError("Can't define method \"%s\" for nil", | |
rb_id2name(node->nd_mid)); | |
} | |
if (rb_special_const_p(recv)) { | |
TypeError("Can't define method \"%s\" for special constants", | |
rb_id2name(node->nd_mid)); | |
} | |
class = rb_singleton_class(recv); | |
if (st_lookup(RCLASS(class)->m_tbl, node->nd_mid, &body)) { | |
Warning("redefine %s", rb_id2name(node->nd_mid)); | |
} | |
rb_clear_cache(); | |
rb_funcall(recv, rb_intern("singleton_method_added"), | |
1, INT2FIX(node->nd_mid)); | |
rb_add_method(class, node->nd_mid, node->nd_defn, NOEX_PUBLIC); | |
result = Qnil; | |
} | |
break; | |
case NODE_UNDEF: | |
{ | |
struct RClass *origin; | |
NODE *body; | |
body = search_method(the_class, node->nd_mid, &origin); | |
if (!body || !body->nd_body) { | |
NameError("undefined method `%s' for class `%s'", | |
rb_id2name(node->nd_mid), rb_class2name((VALUE)the_class)); | |
} | |
rb_clear_cache(); | |
rb_add_method(the_class, node->nd_mid, 0, NOEX_PUBLIC); | |
result = Qnil; | |
} | |
break; | |
case NODE_ALIAS: | |
rb_alias(the_class, node->nd_new, node->nd_old); | |
result = Qnil; | |
break; | |
case NODE_VALIAS: | |
rb_alias_variable(node->nd_new, node->nd_old); | |
result = Qnil; | |
break; | |
case NODE_CLASS: | |
{ | |
VALUE super, class; | |
struct RClass *tmp; | |
if (node->nd_super) { | |
super = superclass(self, node->nd_super); | |
} | |
else { | |
super = 0; | |
} | |
if (rb_const_defined_at(the_class, node->nd_cname) && | |
((VALUE)the_class != cObject || | |
!rb_autoload_defined(node->nd_cname))) { | |
class = rb_const_get_at(the_class, node->nd_cname); | |
if (TYPE(class) != T_CLASS) { | |
TypeError("%s is not a class", rb_id2name(node->nd_cname)); | |
} | |
if (super) { | |
tmp = RCLASS(class)->super; | |
if (FL_TEST(tmp, FL_SINGLETON)) { | |
tmp = RCLASS(tmp)->super; | |
} | |
while (TYPE(tmp) == T_ICLASS) { | |
tmp = RCLASS(tmp)->super; | |
} | |
if (tmp != RCLASS(super)) { | |
TypeError("superclass mismatch for %s", | |
rb_id2name(node->nd_cname)); | |
} | |
} | |
if (safe_level >= 4) { | |
Raise(eSecurityError, "extending class prohibited"); | |
} | |
rb_clear_cache(); | |
Warning("extending class %s", rb_id2name(node->nd_cname)); | |
} | |
else { | |
if (!super) super = cObject; | |
class = rb_define_class_id(node->nd_cname, super); | |
rb_const_set(the_class, node->nd_cname, class); | |
rb_set_class_path(class,the_class,rb_id2name(node->nd_cname)); | |
} | |
result = module_setup(class, node->nd_body); | |
} | |
break; | |
case NODE_MODULE: | |
{ | |
VALUE module; | |
if (rb_const_defined_at(the_class, node->nd_cname) && | |
((VALUE)the_class != cObject || | |
!rb_autoload_defined(node->nd_cname))) { | |
module = rb_const_get_at(the_class, node->nd_cname); | |
if (TYPE(module) != T_MODULE) { | |
TypeError("%s is not a module", rb_id2name(node->nd_cname)); | |
} | |
if (safe_level >= 4) { | |
Raise(eSecurityError, "extending module prohibited"); | |
} | |
Warning("extending module %s", rb_id2name(node->nd_cname)); | |
} | |
else { | |
module = rb_define_module_id(node->nd_cname); | |
rb_const_set(the_class, node->nd_cname, module); | |
rb_set_class_path(module,the_class,rb_id2name(node->nd_cname)); | |
} | |
result = module_setup(module, node->nd_body); | |
} | |
break; | |
case NODE_SCLASS: | |
{ | |
VALUE class; | |
class = rb_eval(self, node->nd_recv); | |
if (FIXNUM_P(class)) { | |
TypeError("No virtual class for Fixnums"); | |
} | |
if (NIL_P(class)) { | |
TypeError("No virtual class for nil"); | |
} | |
if (rb_special_const_p(class)) { | |
TypeError("No virtual class for special constants"); | |
} | |
if (FL_TEST(CLASS_OF(class), FL_SINGLETON)) { | |
rb_clear_cache(); | |
} | |
class = rb_singleton_class(class); | |
result = module_setup(class, node->nd_body); | |
} | |
break; | |
case NODE_DEFINED: | |
{ | |
char buf[20]; | |
char *desc = is_defined(self, node, buf); | |
if (desc) result = str_new2(desc); | |
else result = FALSE; | |
} | |
break; | |
case NODE_NEWLINE: | |
sourcefile = node->file; | |
sourceline = node->nd_nth; | |
if (trace_func) { | |
call_trace_func("line", sourcefile, sourceline, | |
self, the_frame->last_func); | |
} | |
node = node->nd_next; | |
goto again; | |
default: | |
Bug("unknown node type %d", nd_type(node)); | |
} | |
finish: | |
CHECK_INTS; | |
return result; | |
} | |
static VALUE | |
module_setup(module, node) | |
VALUE module; | |
NODE * volatile node; | |
{ | |
NODE *state; | |
VALUE save = the_frame->cbase; | |
VALUE result; /* OK */ | |
/* fill c-ref */ | |
node->nd_clss = module; | |
node = node->nd_body; | |
PUSH_CLASS(); | |
the_class = (struct RClass*)module; | |
PUSH_SCOPE(); | |
if (node->nd_rval) the_frame->cbase = node->nd_rval; | |
if (node->nd_tbl) { | |
VALUE *vars = ALLOCA_N(VALUE, node->nd_tbl[0]+1); | |
*vars++ = (VALUE)node; | |
the_scope->local_vars = vars; | |
memclear(the_scope->local_vars, node->nd_tbl[0]); | |
the_scope->local_tbl = node->nd_tbl; | |
} | |
else { | |
the_scope->local_vars = 0; | |
the_scope->local_tbl = 0; | |
} | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
if (trace_func) { | |
call_trace_func("class", node->file, nd_line(node), | |
the_class, the_frame->last_func); | |
} | |
result = rb_eval((VALUE)the_class, node->nd_body); | |
} | |
POP_TAG(); | |
POP_SCOPE(); | |
POP_CLASS(); | |
the_frame->cbase = save; | |
if (trace_func) { | |
call_trace_func("end", node->file, nd_line(node), 0, | |
the_frame->last_func); | |
} | |
if (state) JUMP_TAG(state); | |
return result; | |
} | |
int | |
rb_respond_to(obj, id) | |
VALUE obj; | |
ID id; | |
{ | |
if (rb_method_boundp(CLASS_OF(obj), id, 0)) { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
static VALUE | |
obj_respond_to(argc, argv, obj) | |
int argc; | |
VALUE *argv; | |
VALUE obj; | |
{ | |
VALUE mid, priv; | |
ID id; | |
rb_scan_args(argc, argv, "11", &mid, &priv); | |
id = rb_to_id(mid); | |
if (rb_method_boundp(CLASS_OF(obj), id, !RTEST(priv))) { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
static VALUE | |
mod_method_defined(mod, mid) | |
VALUE mod, mid; | |
{ | |
if (rb_method_boundp(mod, rb_to_id(mid), 1)) { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
void | |
rb_exit(status) | |
int status; | |
{ | |
if (prot_tag) { | |
exit_status = status; | |
rb_raise(exc_new(eSystemExit, 0, 0)); | |
} | |
exit(status); | |
} | |
static VALUE | |
f_exit(argc, argv, obj) | |
int argc; | |
VALUE *argv; | |
VALUE obj; | |
{ | |
VALUE status; | |
rb_secure(2); | |
if (rb_scan_args(argc, argv, "01", &status) == 1) { | |
status = NUM2INT(status); | |
} | |
else { | |
status = 0; | |
} | |
rb_exit(status); | |
/* not reached */ | |
} | |
static VALUE | |
f_abort() | |
{ | |
rb_secure(2); | |
if (errinfo) { | |
error_print(); | |
} | |
rb_exit(1); | |
/* not reached */ | |
} | |
void | |
rb_break() | |
{ | |
JUMP_TAG2(TAG_BREAK, 0); | |
} | |
static VALUE | |
f_break() | |
{ | |
JUMP_TAG2(TAG_BREAK, 0); | |
} | |
static VALUE | |
f_next() | |
{ | |
JUMP_TAG2(TAG_NEXT, 0); | |
} | |
static VALUE | |
f_redo() | |
{ | |
JUMP_TAG2(TAG_REDO, 0); | |
} | |
static VALUE | |
f_retry() | |
{ | |
JUMP_TAG2(TAG_RETRY, 0); | |
} | |
#ifdef __GNUC__ | |
static volatile voidfn rb_longjmp; | |
#endif | |
static VALUE make_backtrace(); | |
static void | |
rb_longjmp(tag, mesg) | |
int tag; | |
VALUE mesg; | |
{ | |
if (NIL_P(errinfo) && NIL_P(mesg)) { | |
errinfo = exc_new(eRuntimeError, 0, 0); | |
} | |
if (sourcefile && (NIL_P(errat) || !NIL_P(mesg))) { | |
errat = make_backtrace(); | |
} | |
if (!NIL_P(mesg)) { | |
if (obj_is_kind_of(mesg, eGlobalExit)) { | |
errinfo = mesg; | |
} | |
else { | |
errinfo = exc_new3(eRuntimeError, mesg); | |
} | |
str_freeze(errinfo); | |
} | |
JUMP_TAG2(tag, 0); | |
} | |
void | |
rb_raise(mesg) | |
VALUE mesg; | |
{ | |
rb_longjmp(TAG_RAISE, mesg); | |
} | |
void | |
rb_fatal(mesg) | |
VALUE mesg; | |
{ | |
rb_longjmp(TAG_FATAL, mesg); | |
} | |
void | |
rb_interrupt() | |
{ | |
Raise(eInterrupt, ""); | |
} | |
static VALUE | |
f_raise(argc, argv) | |
int argc; | |
VALUE *argv; | |
{ | |
VALUE arg1, arg2; | |
VALUE etype, mesg; | |
int n; | |
etype = eRuntimeError; | |
mesg = Qnil; | |
switch (n = rb_scan_args(argc, argv, "02", &arg1, &arg2)) { | |
case 1: | |
mesg = arg1; | |
break; | |
case 2: | |
etype = arg1; | |
if (obj_is_kind_of(etype, eGlobalExit)) { | |
etype = CLASS_OF(etype); | |
} | |
else { | |
Check_Type(etype, T_CLASS); | |
} | |
mesg = arg2; | |
break; | |
} | |
if (!NIL_P(mesg)) { | |
Check_Type(mesg, T_STRING); | |
if (n == 2 || !obj_is_kind_of(mesg, eException)) { | |
mesg = exc_new3(etype, mesg); | |
} | |
} | |
PUSH_FRAME(); /* fake frame */ | |
*the_frame = *_frame.prev->prev; | |
rb_raise(mesg); | |
POP_FRAME(); | |
} | |
int | |
iterator_p() | |
{ | |
if (the_frame->iter) return TRUE; | |
return FALSE; | |
} | |
static VALUE | |
f_iterator_p() | |
{ | |
if (the_frame->prev && the_frame->prev->iter) return TRUE; | |
return FALSE; | |
} | |
VALUE | |
rb_yield_0(val, self) | |
VALUE val; | |
volatile VALUE self; | |
{ | |
NODE *node; | |
NODE *state; | |
volatile VALUE result = Qnil; | |
struct BLOCK *block; | |
struct SCOPE *old_scope; | |
struct FRAME frame; | |
if (!iterator_p()) { | |
Raise(eLocalJumpError, "yield called out of iterator"); | |
} | |
PUSH_VARS(); | |
PUSH_CLASS(); | |
block = the_block; | |
frame = block->frame; | |
frame.prev = the_frame; | |
the_frame = &(frame); | |
old_scope = the_scope; | |
the_scope = block->scope; | |
the_block = block->prev; | |
the_dyna_vars = block->d_vars; | |
the_class = block->class; | |
if (!self) self = block->self; | |
node = block->body; | |
if (block->var) { | |
if (nd_type(block->var) == NODE_MASGN) | |
massign(self, block->var, val); | |
else | |
assign(self, block->var, val); | |
} | |
PUSH_ITER(block->iter); | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
redo: | |
if (!node) { | |
result = Qnil; | |
} | |
else if (nd_type(node) == NODE_CFUNC) { | |
result = (*node->nd_cfnc)(val, node->nd_argc, self); | |
} | |
else { | |
result = rb_eval(self, node); | |
} | |
} | |
else { | |
switch (state->nd_tag) { | |
case TAG_REDO: | |
state = 0; | |
goto redo; | |
case TAG_NEXT: | |
state = 0; | |
result = Qnil; | |
break; | |
case TAG_BREAK: | |
case TAG_RETURN: | |
state->nd_tlev = block->level; | |
state->nd_tag = IN_BLOCK|state->nd_tag; | |
break; | |
default: | |
break; | |
} | |
} | |
POP_TAG(); | |
POP_ITER(); | |
POP_CLASS(); | |
POP_VARS(); | |
the_block = block; | |
the_frame = the_frame->prev; | |
the_scope = old_scope; | |
if (state) JUMP_TAG(state); | |
return result; | |
} | |
VALUE | |
rb_yield(val) | |
VALUE val; | |
{ | |
return rb_yield_0(val, 0); | |
} | |
static VALUE | |
f_loop() | |
{ | |
for (;;) { rb_yield(Qnil); } | |
} | |
static VALUE | |
massign(self, node, val) | |
VALUE self; | |
NODE *node; | |
VALUE val; | |
{ | |
NODE *list; | |
int i, len; | |
list = node->nd_head; | |
if (val) { | |
if (TYPE(val) != T_ARRAY) { | |
val = rb_to_a(val); | |
} | |
len = RARRAY(val)->len; | |
for (i=0; list && i<len; i++) { | |
assign(self, list->nd_head, RARRAY(val)->ptr[i]); | |
list = list->nd_next; | |
} | |
if (node->nd_args) { | |
if (!list && i<len) { | |
assign(self, node->nd_args, ary_new4(len-i, RARRAY(val)->ptr+i)); | |
} | |
else { | |
assign(self, node->nd_args, ary_new2(0)); | |
} | |
} | |
} | |
else if (node->nd_args) { | |
assign(self, node->nd_args, Qnil); | |
} | |
while (list) { | |
assign(self, list->nd_head, Qnil); | |
list = list->nd_next; | |
} | |
return val; | |
} | |
static void | |
assign(self, lhs, val) | |
VALUE self; | |
NODE *lhs; | |
VALUE val; | |
{ | |
switch (nd_type(lhs)) { | |
case NODE_GASGN: | |
rb_gvar_set(lhs->nd_entry, val); | |
break; | |
case NODE_IASGN: | |
rb_ivar_set(self, lhs->nd_vid, val); | |
break; | |
case NODE_LASGN: | |
if (the_scope->local_vars == 0) | |
Bug("unexpected iterator variable assignment"); | |
the_scope->local_vars[lhs->nd_cnt] = val; | |
break; | |
case NODE_DASGN: | |
dyna_var_asgn(lhs->nd_vid, val); | |
break; | |
case NODE_CASGN: | |
rb_const_set(the_class, lhs->nd_vid, val); | |
break; | |
case NODE_CALL: | |
{ | |
VALUE recv; | |
recv = rb_eval(self, lhs->nd_recv); | |
if (!lhs->nd_args->nd_head) { | |
/* attr set */ | |
rb_funcall2(recv, lhs->nd_mid, 1, &val); | |
} | |
else { | |
/* array set */ | |
VALUE args; | |
args = rb_eval(self, lhs->nd_args); | |
RARRAY(args)->ptr[RARRAY(args)->len-1] = val; | |
rb_apply(recv, lhs->nd_mid, args); | |
} | |
} | |
break; | |
default: | |
Bug("bug in variable assignment"); | |
break; | |
} | |
} | |
VALUE | |
rb_iterate(it_proc, data1, bl_proc, data2) | |
VALUE (*it_proc)(), (*bl_proc)(); | |
void *data1, *data2; | |
{ | |
NODE *state; | |
volatile VALUE retval = Qnil; | |
NODE *node = NEW_CFUNC(bl_proc, data2); | |
VALUE self = TopSelf; | |
int tag_level; | |
iter_retry: | |
PUSH_ITER(ITER_PRE); | |
PUSH_BLOCK(0, node); | |
PUSH_TAG(); | |
state = EXEC_TAG(); | |
if (state == 0) { | |
retval = (*it_proc)(data1); | |
} | |
POP_TAG(); | |
tag_level = the_block->level; | |
POP_BLOCK(); | |
POP_ITER(); | |
if (state) { | |
switch (state->nd_tag) { | |
case TAG_RETRY: | |
goto iter_retry; | |
case IN_BLOCK|TAG_BREAK: | |
if (state->nd_tlev != tag_level) { | |
JUMP_TAG(state); | |
} | |
retval = Qnil; | |
break; | |
case IN_BLOCK|TAG_RETURN: | |
if (state->nd_tlev == tag_level) { | |
state->nd_tag &= ~IN_BLOCK; | |
} | |
/* fall through */ | |
default: | |
JUMP_TAG(state); | |
} | |
} | |
return retval; | |
} | |
static int | |
handle_rescue(self, node) | |
VALUE self; | |
NODE *node; | |
{ | |
int argc; VALUE *argv; /* used in SETUP_ARGS */ | |
if (!node->nd_args) { | |
return obj_is_kind_of(errinfo, eException); | |
} | |
PUSH_ITER(ITER_NOT); | |
SETUP_ARGS(node->nd_args); | |
POP_ITER(); | |
while (argc--) { | |
if (!obj_is_kind_of(argv[0], cModule)) { | |
TypeError("class or module required for rescue clause"); | |
} | |
if (obj_is_kind_of(errinfo, argv[0])) return 1; | |
argv++; | |
} | |
return 0; | |
} | |
VALUE | |
rb_rescue(b_proc, data1, r_proc, data2) | |
VALUE (*b_proc)(), (*r_proc)(); | |
void *data1, *data2; | |
{ | |
NODE *state; | |
volatile VALUE result; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
retry_entry: | |
result = (*b_proc)(data1); | |
} | |
else { | |
if (state->nd_tag == TAG_RAISE) { | |
if (r_proc) { | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
result = (*r_proc)(data2, errinfo); | |
} | |
POP_TAG(); | |
if (state && state->nd_tag == TAG_RETRY) { | |
state = 0; | |
goto retry_entry; | |
} | |
} | |
else { | |
result = Qnil; | |
state = 0; | |
} | |
if (state == 0) { | |
errat = Qnil; | |
} | |
} | |
} | |
POP_TAG(); | |
if (state) JUMP_TAG(state); | |
return result; | |
} | |
VALUE | |
rb_ensure(b_proc, data1, e_proc, data2) | |
VALUE (*b_proc)(), (*e_proc)(); | |
void *data1, *data2; | |
{ | |
NODE *state; | |
volatile VALUE result = Qnil; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
result = (*b_proc)(data1); | |
} | |
POP_TAG(); | |
(*e_proc)(data2); | |
if (state) { | |
JUMP_TAG(state); | |
} | |
return result; | |
} | |
static int last_call_status; | |
#define CSTAT_NOEX 1 | |
#define CSTAT_VCALL 2 | |
static VALUE | |
f_missing(argc, argv, obj) | |
int argc; | |
VALUE *argv; | |
VALUE obj; | |
{ | |
VALUE desc = 0; | |
ID id; | |
char *format = 0; | |
char *file = sourcefile; | |
int line = sourceline; | |
id = FIX2INT(argv[0]); | |
argc--; argv++; | |
switch (TYPE(obj)) { | |
case T_NIL: | |
format = "undefined method `%s' for nil"; | |
break; | |
case T_TRUE: | |
format = "undefined method `%s' for TRUE"; | |
break; | |
case T_FALSE: | |
format = "undefined method `%s' for FALSE"; | |
break; | |
case T_OBJECT: | |
desc = obj_as_string(obj); | |
break; | |
default: | |
desc = rb_inspect(obj); | |
break; | |
} | |
if (desc) { | |
if (last_call_status & CSTAT_NOEX) { | |
format = "private method `%s' called for %s(%s)"; | |
} | |
else if (iterator_p()) { | |
format = "undefined iterator `%s' for %s(%s)"; | |
} | |
else if (last_call_status & CSTAT_VCALL) { | |
char *mname = rb_id2name(id); | |
if (('a' <= mname[0] && mname[0] <= 'z') || mname[0] == '_') { | |
format = "undefined local variable or method `%s' for %s(%s)"; | |
} | |
} | |
if (!format) { | |
format = "undefined method `%s' for %s(%s)"; | |
} | |
if (RSTRING(desc)->len > 65) { | |
desc = any_to_s(obj); | |
} | |
} | |
sourcefile = file; | |
sourceline = line; | |
PUSH_FRAME(); /* fake frame */ | |
*the_frame = *_frame.prev->prev; | |
NameError(format, | |
rb_id2name(id), | |
desc?(char*)RSTRING(desc)->ptr:"", | |
desc?rb_class2name(CLASS_OF(obj)):""); | |
POP_FRAME(); | |
return Qnil; /* not reached */ | |
} | |
static VALUE | |
rb_undefined(obj, id, argc, argv, call_status) | |
VALUE obj; | |
ID id; | |
int argc; | |
VALUE*argv; | |
int call_status; | |
{ | |
VALUE *nargv; | |
nargv = ALLOCA_N(VALUE, argc+1); | |
nargv[0] = INT2FIX(id); | |
MEMCPY(nargv+1, argv, VALUE, argc); | |
last_call_status = call_status; | |
return rb_funcall2(obj, rb_intern("method_missing"), argc+1, nargv); | |
} | |
#ifdef DJGPP | |
# define STACK_LEVEL_MAX 65535 | |
#else | |
#ifdef __human68k__ | |
extern int _stacksize; | |
# define STACK_LEVEL_MAX (_stacksize - 4096) | |
#else | |
# define STACK_LEVEL_MAX 655350 | |
#endif | |
#endif | |
extern VALUE *gc_stack_start; | |
static int | |
stack_length() | |
{ | |
VALUE pos; | |
#ifdef sparc | |
return gc_stack_start - &pos + 0x80; | |
#else | |
return (&pos < gc_stack_start) ? gc_stack_start - &pos | |
: &pos - gc_stack_start; | |
#endif | |
} | |
static VALUE | |
rb_call(class, recv, mid, argc, argv, scope) | |
struct RClass *class; | |
VALUE recv; | |
ID mid; | |
int argc; /* OK */ | |
VALUE *argv; /* OK */ | |
int scope; | |
{ | |
NODE *body, *b2; /* OK */ | |
int noex; | |
ID id = mid; | |
struct cache_entry *ent; | |
volatile VALUE result = Qnil; | |
int itr; | |
enum node_type type; | |
static int tick; | |
again: | |
/* is it in the method cache? */ | |
ent = cache + EXPR1(class, mid); | |
if (ent->mid == mid && ent->class == class) { | |
class = ent->origin; | |
id = ent->mid0; | |
noex = ent->noex; | |
body = ent->method; | |
} | |
else if ((body = rb_get_method_body(&class, &id, &noex)) == 0) { | |
return rb_undefined(recv, mid, argc, argv, scope==2?CSTAT_VCALL:0); | |
} | |
/* receiver specified form for private method */ | |
if (noex == NOEX_PRIVATE && scope == 0) | |
return rb_undefined(recv, mid, argc, argv, CSTAT_NOEX); | |
switch (the_iter->iter) { | |
case ITER_PRE: | |
itr = ITER_CUR; | |
break; | |
case ITER_CUR: | |
default: | |
itr = ITER_NOT; | |
break; | |
} | |
type = nd_type(body); | |
if (type == NODE_ZSUPER) { | |
/* for re-scoped/renamed method */ | |
mid = id; | |
if (scope == 0) scope = 1; | |
if (class->super == 0) { | |
/* origin is the Module, so need to scan superclass hierarchy. */ | |
struct RClass *cl = class; | |
class = (struct RClass*)RBASIC(recv)->class; | |
while (class) { | |
if (class->m_tbl == cl->m_tbl) | |
break; | |
class = class->super; | |
} | |
} | |
else { | |
class = class->super; | |
} | |
goto again; | |
} | |
if ((++tick & 0xfff) == 0 && stack_length() > STACK_LEVEL_MAX) | |
Fatal("stack level too deep"); | |
PUSH_ITER(itr); | |
PUSH_FRAME(); | |
the_frame->last_func = id; | |
the_frame->last_class = class; | |
the_frame->argc = argc; | |
the_frame->argv = argv; | |
switch (type) { | |
case NODE_CFUNC: | |
{ | |
int len = body->nd_argc; | |
if (len >= 0 && argc != len) { | |
ArgError("Wrong # of arguments(%d for %d)", argc, len); | |
} | |
switch (len) { | |
case -2: | |
result = (*body->nd_cfnc)(recv, ary_new4(argc, argv)); | |
break; | |
case -1: | |
result = (*body->nd_cfnc)(argc, argv, recv); | |
break; | |
case 0: | |
result = (*body->nd_cfnc)(recv); | |
break; | |
case 1: | |
result = (*body->nd_cfnc)(recv, argv[0]); | |
break; | |
case 2: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1]); | |
break; | |
case 3: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2]); | |
break; | |
case 4: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3]); | |
break; | |
case 5: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4]); | |
break; | |
case 6: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5]); | |
break; | |
case 7: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6]); | |
break; | |
case 8: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6], argv[7]); | |
break; | |
case 9: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6], argv[7], argv[8]); | |
break; | |
case 10: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6], argv[7], argv[8], | |
argv[6], argv[7], argv[8], | |
argv[9]); | |
break; | |
case 11: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6], argv[7], argv[8], | |
argv[6], argv[7], argv[8], | |
argv[9], argv[10]); | |
break; | |
case 12: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6], argv[7], argv[8], | |
argv[6], argv[7], argv[8], | |
argv[9], argv[10], argv[11]); | |
break; | |
case 13: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6], argv[7], argv[8], | |
argv[6], argv[7], argv[8], | |
argv[9], argv[10], argv[11], | |
argv[12]); | |
break; | |
case 14: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6], argv[7], argv[8], | |
argv[6], argv[7], argv[8], | |
argv[9], argv[10], argv[11], | |
argv[12], argv[13]); | |
break; | |
case 15: | |
result = (*body->nd_cfnc)(recv, argv[0], argv[1], argv[2], | |
argv[3], argv[4], argv[5], | |
argv[6], argv[7], argv[8], | |
argv[6], argv[7], argv[8], | |
argv[9], argv[10], argv[11], | |
argv[12], argv[13], argv[14]); | |
break; | |
default: | |
if (len < 0) { | |
Bug("bad argc(%d) specified for `%s(%s)'", | |
len, rb_class2name((VALUE)class), rb_id2name(mid)); | |
} | |
else { | |
ArgError("too many arguments(%d)", len); | |
} | |
break; | |
} | |
} | |
break; | |
/* for attr get/set */ | |
case NODE_ATTRSET: | |
case NODE_IVAR: | |
result = rb_eval(recv, body); | |
break; | |
default: | |
{ | |
NODE *state; | |
VALUE *local_vars; | |
PUSH_SCOPE(); | |
if (body->nd_rval) the_frame->cbase = body->nd_rval; | |
if (body->nd_tbl) { | |
local_vars = ALLOCA_N(VALUE, body->nd_tbl[0]+1); | |
*local_vars++ = (VALUE)body; | |
memclear(local_vars, body->nd_tbl[0]); | |
the_scope->local_tbl = body->nd_tbl; | |
the_scope->local_vars = local_vars; | |
} | |
else { | |
local_vars = the_scope->local_vars = 0; | |
the_scope->local_tbl = 0; | |
} | |
b2 = body = body->nd_body; | |
PUSH_TAG(); | |
PUSH_VARS(); | |
if ((state = EXEC_TAG()) == 0) { | |
if (nd_type(body) == NODE_BLOCK) { | |
NODE *node = body->nd_head; | |
int i; | |
if (nd_type(node) != NODE_ARGS) { | |
Bug("no argument-node"); | |
} | |
body = body->nd_next; | |
i = node->nd_cnt; | |
if (i > argc) { | |
ArgError("Wrong # of arguments(%d for %d)", argc, i); | |
} | |
if (node->nd_rest == -1) { | |
int opt = argc - i; | |
NODE *optnode = node->nd_opt; | |
while (optnode) { | |
opt--; | |
optnode = optnode->nd_next; | |
} | |
if (opt > 0) { | |
ArgError("Wrong # of arguments(%d for %d)", | |
argc, argc-opt); | |
} | |
} | |
if (local_vars) { | |
if (i > 0) { | |
MEMCPY(local_vars, argv, VALUE, i); | |
} | |
argv += i; argc -= i; | |
if (node->nd_opt) { | |
NODE *opt = node->nd_opt; | |
while (opt && argc) { | |
assign(recv, opt->nd_head, *argv); | |
argv++; argc--; | |
opt = opt->nd_next; | |
} | |
rb_eval(recv, opt); | |
} | |
if (node->nd_rest >= 0) { | |
if (argc > 0) | |
local_vars[node->nd_rest]=ary_new4(argc,argv); | |
else | |
local_vars[node->nd_rest]=ary_new2(0); | |
} | |
} | |
} | |
else if (nd_type(body) == NODE_ARGS) { | |
body = 0; | |
} | |
if (trace_func) { | |
call_trace_func("call", b2->file, nd_line(b2), | |
recv, the_frame->last_func); | |
} | |
result = rb_eval(recv, body); | |
} | |
POP_VARS(); | |
POP_TAG(); | |
POP_SCOPE(); | |
if (trace_func) { | |
char *file = the_frame->prev->file; | |
int line = the_frame->prev->line; | |
if (!file) { | |
file = sourcefile; | |
line = sourceline; | |
} | |
call_trace_func("return", file, line, 0, the_frame->last_func); | |
} | |
if (state) { | |
switch (state->nd_tag) { | |
case TAG_NEXT: | |
Raise(eLocalJumpError, "unexpected next"); | |
break; | |
case TAG_BREAK: | |
Raise(eLocalJumpError, "unexpected break"); | |
break; | |
case TAG_REDO: | |
Raise(eLocalJumpError, "unexpected redo"); | |
break; | |
case TAG_RETURN: | |
result = state->nd_tval; | |
break; | |
case TAG_RETRY: | |
if (!iterator_p()) { | |
Raise(eLocalJumpError, "retry outside of rescue clause"); | |
} | |
default: | |
JUMP_TAG(state); | |
} | |
} | |
} | |
} | |
POP_FRAME(); | |
POP_ITER(); | |
return result; | |
} | |
VALUE | |
rb_apply(recv, mid, args) | |
VALUE recv; | |
struct RArray *args; | |
ID mid; | |
{ | |
int argc; | |
VALUE *argv; | |
argc = RARRAY(args)->len; | |
argv = ALLOCA_N(VALUE, argc); | |
MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc); | |
return rb_call(CLASS_OF(recv), recv, mid, argc, argv, 1); | |
} | |
static VALUE | |
f_send(argc, argv, recv) | |
int argc; | |
VALUE *argv; | |
VALUE recv; | |
{ | |
VALUE vid; | |
ID mid; | |
if (argc == 0) ArgError("no method name given"); | |
vid = argv[0]; argc--; argv++; | |
if (TYPE(vid) == T_STRING) { | |
mid = rb_intern(RSTRING(vid)->ptr); | |
} | |
else { | |
mid = NUM2INT(vid); | |
} | |
PUSH_ITER(iterator_p()?ITER_PRE:ITER_NOT); | |
vid = rb_call(CLASS_OF(recv), recv, mid, argc, argv, 1); | |
POP_ITER(); | |
return vid; | |
} | |
#include <varargs.h> | |
VALUE | |
rb_funcall(recv, mid, n, va_alist) | |
VALUE recv; | |
ID mid; | |
int n; | |
va_dcl | |
{ | |
va_list ar; | |
VALUE *argv; | |
if (n > 0) { | |
int i; | |
argv = ALLOCA_N(VALUE, n); | |
va_start(ar); | |
for (i=0;i<n;i++) { | |
argv[i] = va_arg(ar, VALUE); | |
} | |
va_end(ar); | |
} | |
else { | |
argv = 0; | |
} | |
return rb_call(CLASS_OF(recv), recv, mid, n, argv, 1); | |
} | |
VALUE | |
rb_funcall2(recv, mid, argc, argv) | |
VALUE recv; | |
ID mid; | |
int argc; | |
VALUE *argv; | |
{ | |
return rb_call(CLASS_OF(recv), recv, mid, argc, argv, 1); | |
} | |
static VALUE | |
backtrace(lev) | |
int lev; | |
{ | |
struct FRAME *frame = the_frame; | |
char buf[BUFSIZ]; | |
VALUE ary; | |
int slev = safe_level; | |
safe_level = 0; | |
ary = ary_new(); | |
if (lev < 0) { | |
if (frame->last_func) { | |
sprintf(buf, "%s:%d:in `%s'", sourcefile, sourceline, | |
rb_id2name(frame->last_func)); | |
} | |
else { | |
sprintf(buf, "%s:%d", sourcefile, sourceline); | |
} | |
ary_push(ary, str_new2(buf)); | |
} | |
else { | |
while (lev-- > 0) { | |
frame = frame->prev; | |
if (!frame) return Qnil; | |
} | |
} | |
while (frame && frame->file) { | |
if (frame->prev && frame->prev->last_func) { | |
sprintf(buf, "%s:%d:in `%s'", | |
frame->file, frame->line, | |
rb_id2name(frame->prev->last_func)); | |
} | |
else { | |
sprintf(buf, "%s:%d", frame->file, frame->line); | |
} | |
ary_push(ary, str_new2(buf)); | |
frame = frame->prev; | |
} | |
safe_level = slev; | |
return ary; | |
} | |
static VALUE | |
f_caller(argc, argv) | |
int argc; | |
VALUE *argv; | |
{ | |
VALUE level; | |
int lev; | |
rb_scan_args(argc, argv, "01", &level); | |
if (NIL_P(level)) lev = 1; | |
else lev = NUM2INT(level); | |
if (lev < 0) ArgError("negative level(%d)", lev); | |
return backtrace(lev); | |
} | |
void | |
rb_backtrace() | |
{ | |
int i, lev; | |
VALUE ary; | |
lev = INT2FIX(0); | |
ary = backtrace(-1); | |
for (i=0; i<RARRAY(ary)->len; i++) { | |
printf("\tfrom %s\n", RSTRING(RARRAY(ary)->ptr[i])->ptr); | |
} | |
} | |
static VALUE | |
make_backtrace() | |
{ | |
VALUE lev; | |
lev = INT2FIX(0); | |
return backtrace(-1); | |
} | |
ID | |
rb_frame_last_func() | |
{ | |
return the_frame->last_func; | |
} | |
static NODE* | |
compile(src) | |
struct RString *src; | |
{ | |
NODE *node; | |
Check_Type(src, T_STRING); | |
node = compile_string(sourcefile, src->ptr, src->len); | |
if (nerrs == 0) return node; | |
return 0; | |
} | |
static VALUE | |
eval(self, src, scope) | |
VALUE self; | |
struct RString *src; | |
struct RData *scope; | |
{ | |
struct BLOCK *data; | |
volatile VALUE result = Qnil; | |
NODE *state; | |
volatile VALUE old_block; | |
volatile VALUE old_scope; | |
volatile VALUE old_d_vars; | |
struct FRAME frame; | |
char *file = sourcefile; | |
int line = sourceline; | |
volatile int iter = the_frame->iter; | |
if (!NIL_P(scope)) { | |
if (TYPE(scope) != T_DATA || scope->dfree != blk_free) { | |
TypeError("wrong argument type %s (expected Proc/Binding)", | |
rb_class2name(CLASS_OF(scope))); | |
} | |
Data_Get_Struct(scope, struct BLOCK, data); | |
/* PUSH BLOCK from data */ | |
frame = data->frame; | |
frame.prev = the_frame; | |
the_frame = &(frame); | |
old_scope = (VALUE)the_scope; | |
the_scope = data->scope; | |
old_block = (VALUE)the_block; | |
the_block = data->prev; | |
old_d_vars = (VALUE)the_dyna_vars; | |
the_dyna_vars = data->d_vars; | |
self = data->self; | |
the_frame->iter = data->iter; | |
} | |
else { | |
if (the_frame->prev) { | |
the_frame->iter = the_frame->prev->iter; | |
} | |
} | |
PUSH_CLASS(); | |
the_class = (struct RClass*)((NODE*)the_frame->cbase)->nd_clss; | |
rb_in_eval++; | |
if (TYPE(the_class) == T_ICLASS) { | |
the_class = (struct RClass*)RBASIC(the_class)->class; | |
} | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
compile(src); | |
if (nerrs > 0) { | |
compile_error("eval()"); | |
} | |
result = eval_node(self); | |
} | |
POP_TAG(); | |
POP_CLASS(); | |
rb_in_eval--; | |
if (!NIL_P(scope)) { | |
the_frame = the_frame->prev; | |
the_scope = (struct SCOPE*)old_scope; | |
the_block = (struct BLOCK*)old_block; | |
the_dyna_vars = (struct RVarmap*)old_d_vars; | |
} | |
else { | |
the_frame->iter = iter; | |
} | |
if (state) { | |
VALUE err ; | |
switch (state->nd_tag) { | |
case TAG_RAISE: | |
sourcefile = file; | |
sourceline = line; | |
if (strcmp(sourcefile, "(eval)") == 0) { | |
err = errinfo; | |
if (sourceline > 1) { | |
err = RARRAY(errat)->ptr[0]; | |
str_cat(err, ": ", 2); | |
str_cat(err, RSTRING(errinfo)->ptr, RSTRING(errinfo)->len); | |
} | |
errat = Qnil; | |
rb_raise(exc_new3(CLASS_OF(errinfo), err)); | |
} | |
rb_raise(Qnil); | |
} | |
JUMP_TAG(state); | |
} | |
return result; | |
} | |
static VALUE | |
f_eval(argc, argv, self) | |
int argc; | |
VALUE *argv; | |
VALUE self; | |
{ | |
VALUE src, scope; | |
rb_scan_args(argc, argv, "11", &src, &scope); | |
Check_SafeStr(src); | |
return eval(self, src, scope); | |
} | |
VALUE rb_load_path; | |
char *dln_find_file(); | |
static char* | |
find_file(file) | |
char *file; | |
{ | |
extern VALUE rb_load_path; | |
VALUE vpath; | |
char *path; | |
if (file[0] == '/') return file; | |
#if defined(MSDOS) || defined(NT) || defined(__human68k__) | |
if (file[0] == '\\') return file; | |
if (file[1] == ':') return file; | |
#endif | |
if (rb_load_path) { | |
int i; | |
Check_Type(rb_load_path, T_ARRAY); | |
for (i=0;i<RARRAY(rb_load_path)->len;i++) { | |
Check_SafeStr(RARRAY(rb_load_path)->ptr[i]); | |
} | |
#if !defined(MSDOS) && !defined(NT) && !defined(__human68k__) | |
vpath = ary_join(rb_load_path, str_new2(":")); | |
#else | |
vpath = ary_join(rb_load_path, str_new2(";")); | |
#endif | |
Check_SafeStr(vpath); | |
path = RSTRING(vpath)->ptr; | |
} | |
else { | |
path = 0; | |
} | |
return dln_find_file(file, path); | |
} | |
VALUE | |
f_load(obj, fname) | |
VALUE obj; | |
struct RString *fname; | |
{ | |
NODE *state; | |
char *file; | |
volatile ID last_func; | |
Check_SafeStr(fname); | |
if (fname->ptr[0] == '~') { | |
fname = (struct RString*)file_s_expand_path(0, fname); | |
} | |
file = find_file(fname->ptr); | |
if (!file) LoadError("No such file to load -- %s", fname->ptr); | |
PUSH_TAG(); | |
PUSH_CLASS(); | |
the_class = (struct RClass*)cObject; | |
PUSH_SCOPE(); | |
if (top_scope->local_tbl) { | |
int len = top_scope->local_tbl[0]+1; | |
ID *tbl = ALLOC_N(ID, len); | |
VALUE *vars = ALLOCA_N(VALUE, len); | |
*vars++ = 0; | |
MEMCPY(tbl, top_scope->local_tbl, ID, len); | |
MEMCPY(vars, top_scope->local_vars, ID, len-1); | |
the_scope->local_tbl = tbl; | |
the_scope->local_vars = vars; | |
} | |
state = EXEC_TAG(); | |
last_func = the_frame->last_func; | |
the_frame->last_func = 0; | |
if (state == 0) { | |
rb_in_eval++; | |
rb_load_file(file); | |
rb_in_eval--; | |
if (nerrs == 0) { | |
eval_node(TopSelf); | |
} | |
} | |
the_frame->last_func = last_func; | |
if (the_scope->flag == SCOPE_ALLOCA && the_scope->local_tbl) { | |
free(the_scope->local_tbl); | |
} | |
POP_SCOPE(); | |
POP_CLASS(); | |
POP_TAG(); | |
if (nerrs > 0) { | |
rb_raise(errinfo); | |
} | |
if (state) JUMP_TAG(state); | |
return TRUE; | |
} | |
static VALUE rb_features; | |
static int | |
rb_provided(feature) | |
char *feature; | |
{ | |
struct RArray *features = RARRAY(rb_features); | |
VALUE *p, *pend; | |
char *f; | |
int len; | |
p = features->ptr; | |
pend = p + features->len; | |
while (p < pend) { | |
Check_Type(*p, T_STRING); | |
f = RSTRING(*p)->ptr; | |
if (strcmp(f, feature) == 0) return TRUE; | |
len = strlen(feature); | |
if (strncmp(f, feature, len) == 0 | |
&& (strcmp(f+len, ".rb") == 0 ||strcmp(f+len, ".o") == 0)) { | |
return TRUE; | |
} | |
p++; | |
} | |
return FALSE; | |
} | |
#ifdef THREAD | |
static int thread_loading(); | |
static void thread_loading_done(); | |
#endif | |
void | |
rb_provide(feature) | |
char *feature; | |
{ | |
char *buf, *ext; | |
if (!rb_provided(feature)) { | |
ext = strrchr(feature, '.'); | |
if (strcmp(DLEXT, ext) == 0) { | |
buf = ALLOCA_N(char, strlen(feature)+1); | |
strcpy(buf, feature); | |
ext = strrchr(buf, '.'); | |
strcpy(ext, ".o"); | |
feature = buf; | |
} | |
ary_push(rb_features, str_new2(feature)); | |
} | |
} | |
VALUE | |
f_require(obj, fname) | |
VALUE obj; | |
struct RString *fname; | |
{ | |
char *ext, *file, *feature, *buf; | |
volatile VALUE load; | |
Check_SafeStr(fname); | |
if (rb_provided(fname->ptr)) | |
return FALSE; | |
ext = strrchr(fname->ptr, '.'); | |
if (ext) { | |
if (strcmp(".rb", ext) == 0) { | |
feature = file = fname->ptr; | |
file = find_file(file); | |
if (file) goto rb_load; | |
} | |
else if (strcmp(".o", ext) == 0) { | |
file = feature = fname->ptr; | |
if (strcmp(".o", DLEXT) != 0) { | |
buf = ALLOCA_N(char, strlen(fname->ptr)+sizeof(DLEXT)+1); | |
strcpy(buf, feature); | |
ext = strrchr(buf, '.'); | |
strcpy(ext, DLEXT); | |
file = find_file(buf); | |
} | |
if (file) goto dyna_load; | |
} | |
else if (strcmp(DLEXT, ext) == 0) { | |
feature = fname->ptr; | |
file = find_file(feature); | |
if (file) goto dyna_load; | |
} | |
} | |
buf = ALLOCA_N(char, strlen(fname->ptr) + 5); | |
sprintf(buf, "%s.rb", fname->ptr); | |
file = find_file(buf); | |
if (file) { | |
fname = (struct RString*)str_new2(file); | |
feature = buf; | |
goto rb_load; | |
} | |
sprintf(buf, "%s%s", fname->ptr, DLEXT); | |
file = find_file(buf); | |
if (file) { | |
feature = buf; | |
goto dyna_load; | |
} | |
LoadError("No such file to load -- %s", fname->ptr); | |
dyna_load: | |
#ifdef THREAD | |
if (thread_loading(feature)) return FALSE; | |
else { | |
NODE *state; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
#endif | |
load = str_new2(file); | |
file = RSTRING(load)->ptr; | |
dln_load(file); | |
rb_provide(feature); | |
#ifdef THREAD | |
} | |
POP_TAG(); | |
thread_loading_done(); | |
if (state) JUMP_TAG(state); | |
} | |
#endif | |
return TRUE; | |
rb_load: | |
#ifdef THREAD | |
if (thread_loading(feature)) return FALSE; | |
else { | |
NODE *state; | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
#endif | |
f_load(obj, fname); | |
rb_provide(feature); | |
#ifdef THREAD | |
} | |
POP_TAG(); | |
thread_loading_done(); | |
if (state) JUMP_TAG(state); | |
} | |
#endif | |
return TRUE; | |
} | |
static void | |
set_method_visibility(self, argc, argv, ex) | |
VALUE self; | |
int argc; | |
VALUE *argv; | |
int ex; | |
{ | |
int i; | |
for (i=0; i<argc; i++) { | |
rb_export_method(self, rb_to_id(argv[i]), ex); | |
} | |
} | |
static VALUE | |
mod_public(argc, argv, module) | |
int argc; | |
VALUE *argv; | |
VALUE module; | |
{ | |
set_method_visibility(module, argc, argv, NOEX_PUBLIC); | |
return module; | |
} | |
static VALUE | |
mod_private(argc, argv, module) | |
int argc; | |
VALUE *argv; | |
VALUE module; | |
{ | |
set_method_visibility(module, argc, argv, NOEX_PRIVATE); | |
return module; | |
} | |
static VALUE | |
mod_public_method(argc, argv, obj) | |
int argc; | |
VALUE *argv; | |
VALUE obj; | |
{ | |
set_method_visibility(CLASS_OF(obj), argc, argv, NOEX_PUBLIC); | |
return obj; | |
} | |
static VALUE | |
mod_private_method(argc, argv, obj) | |
int argc; | |
VALUE *argv; | |
VALUE obj; | |
{ | |
set_method_visibility(CLASS_OF(obj), argc, argv, NOEX_PRIVATE); | |
return obj; | |
} | |
static VALUE | |
mod_modfunc(argc, argv, module) | |
int argc; | |
VALUE *argv; | |
VALUE module; | |
{ | |
int i; | |
ID id; | |
NODE *body; | |
rb_clear_cache(); | |
set_method_visibility(module, argc, argv, NOEX_PRIVATE); | |
for (i=0; i<argc; i++) { | |
id = rb_to_id(argv[i]); | |
body = search_method(module, id, 0); | |
if (body == 0 || body->nd_body == 0) { | |
NameError("undefined method `%s' for module `%s'", | |
rb_id2name(id), rb_class2name(module)); | |
} | |
rb_add_method(rb_singleton_class(module), id, body->nd_body, NOEX_PUBLIC); | |
} | |
return module; | |
} | |
static VALUE | |
mod_include(argc, argv, module) | |
int argc; | |
VALUE *argv; | |
VALUE module; | |
{ | |
int i; | |
for (i=0; i<argc; i++) { | |
Check_Type(argv[i], T_MODULE); | |
rb_include_module(module, argv[i]); | |
} | |
return module; | |
} | |
VALUE | |
class_s_new(argc, argv, class) | |
int argc; | |
VALUE *argv; | |
VALUE class; | |
{ | |
VALUE obj = obj_alloc(class); | |
if (FL_TEST(class, FL_SINGLETON)) { | |
TypeError("can't create instance of virtual class"); | |
} | |
obj = obj_alloc(class); | |
PUSH_ITER(iterator_p()?ITER_PRE:ITER_NOT); | |
rb_funcall2(obj, init, argc, argv); | |
POP_ITER(); | |
return obj; | |
} | |
VALUE | |
class_new_instance(argc, argv, class) | |
int argc; | |
VALUE *argv; | |
VALUE class; | |
{ | |
VALUE obj; | |
if (FL_TEST(class, FL_SINGLETON)) { | |
TypeError("can't create instance of virtual class"); | |
} | |
obj = obj_alloc(class); | |
PUSH_ITER(iterator_p()?ITER_PRE:ITER_NOT); | |
rb_funcall2(obj, init, argc, argv); | |
POP_ITER(); | |
return obj; | |
} | |
static VALUE | |
top_include(argc, argv) | |
int argc; | |
VALUE *argv; | |
{ | |
rb_secure(4); | |
return mod_include(argc, argv, cObject); | |
} | |
void | |
rb_extend_object(obj, module) | |
VALUE obj, module; | |
{ | |
rb_include_module(rb_singleton_class(obj), module); | |
} | |
static VALUE | |
mod_extend_object(mod, obj) | |
VALUE mod, obj; | |
{ | |
rb_extend_object(obj, mod); | |
return obj; | |
} | |
static VALUE | |
obj_extend(argc, argv, obj) | |
int argc; | |
VALUE *argv; | |
VALUE obj; | |
{ | |
int i; | |
for (i=0; i<argc; i++) Check_Type(argv[i], T_MODULE); | |
for (i=0; i<argc; i++) { | |
rb_funcall(argv[i], rb_intern("extend_object"), 1, obj); | |
} | |
return obj; | |
} | |
VALUE f_trace_var(); | |
VALUE f_untrace_var(); | |
extern void rb_str_setter(); | |
static void | |
errat_setter(val, id, var) | |
VALUE val; | |
ID id; | |
VALUE *var; | |
{ | |
int i; | |
static char *err = "value of $@ must be Array of String"; | |
if (!NIL_P(val)) { | |
if (TYPE(val) != T_ARRAY) { | |
TypeError(err); | |
} | |
for (i=0;i<RARRAY(val)->len;i++) { | |
if (TYPE(RARRAY(val)->ptr[i]) != T_STRING) { | |
TypeError(err); | |
} | |
} | |
} | |
*var = val; | |
} | |
static VALUE | |
f_catch(dmy, tag) | |
VALUE dmy, tag; | |
{ | |
NODE *state; | |
ID t; | |
VALUE val; | |
t = rb_to_id(tag); | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
val = rb_yield(tag); | |
} | |
POP_TAG(); | |
if (state) { | |
if (state->nd_tag == TAG_THROW && state->nd_tlev == t) { | |
return state->nd_tval; | |
} | |
JUMP_TAG(state); | |
} | |
return val; | |
} | |
static VALUE | |
f_throw(argc, argv) | |
int argc; | |
VALUE *argv; | |
{ | |
VALUE tag, value; | |
ID t; | |
rb_scan_args(argc, argv, "11", &tag, &value); | |
t = rb_to_id(tag); | |
JUMP_TAG3(TAG_THROW, value, t); | |
/* not reached */ | |
} | |
void | |
Init_eval() | |
{ | |
init = rb_intern("initialize"); | |
eqq = rb_intern("==="); | |
each = rb_intern("each"); | |
aref = rb_intern("[]"); | |
aset = rb_intern("[]="); | |
rb_global_variable(&top_scope); | |
rb_global_variable(&eval_tree); | |
rb_global_variable(&the_dyna_vars); | |
rb_define_hooked_variable("$@", &errat, 0, errat_setter); | |
rb_define_hooked_variable("$!", &errinfo, 0, rb_str_setter); | |
rb_define_global_function("eval", f_eval, -1); | |
rb_define_global_function("iterator?", f_iterator_p, 0); | |
rb_define_global_function("method_missing", f_missing, -1); | |
rb_define_global_function("loop", f_loop, 0); | |
rb_define_method(mKernel, "respond_to?", obj_respond_to, -1); | |
rb_define_global_function("break", f_break, 0); | |
rb_define_alias(mKernel, "break!", "break"); | |
rb_define_global_function("next", f_next, 0); | |
rb_define_alias(mKernel, "next!", "next"); | |
rb_define_alias(mKernel, "continue", "next"); | |
rb_define_global_function("redo", f_redo, 0); | |
rb_define_alias(mKernel, "redo!", "redo"); | |
rb_define_global_function("retry", f_retry, 0); | |
rb_define_alias(mKernel, "retry!", "retry"); | |
rb_define_global_function("raise", f_raise, -1); | |
rb_define_alias(mKernel, "fail", "raise"); | |
rb_define_global_function("caller", f_caller, -1); | |
rb_define_global_function("exit", f_exit, -1); | |
rb_define_global_function("abort", f_abort, 0); | |
rb_define_global_function("catch", f_catch, 1); | |
rb_define_global_function("throw", f_throw, -1); | |
rb_define_method(mKernel, "send", f_send, -1); | |
rb_define_private_method(cModule, "include", mod_include, -1); | |
rb_define_private_method(cModule, "public", mod_public, -1); | |
rb_define_private_method(cModule, "private", mod_private, -1); | |
rb_define_private_method(cModule, "module_function", mod_modfunc, -1); | |
rb_define_method(cModule, "method_defined?", mod_method_defined, 1); | |
rb_define_method(cModule, "extend_object", mod_extend_object, 1); | |
rb_define_method(cModule, "public_class_method", mod_public_method, -1); | |
rb_define_method(cModule, "private_class_method", mod_private_method, -1); | |
rb_define_method(CLASS_OF(TopSelf), "include", top_include, -1); | |
rb_define_method(mKernel, "extend", obj_extend, -1); | |
rb_define_global_function("trace_var", f_trace_var, -1); | |
rb_define_global_function("untrace_var", f_untrace_var, -1); | |
rb_define_global_function("set_trace_func", set_trace_func, 1); | |
rb_define_virtual_variable("$SAFE", safe_getter, safe_setter); | |
} | |
VALUE f_autoload(); | |
void | |
Init_load() | |
{ | |
rb_load_path = ary_new(); | |
rb_define_readonly_variable("$:", &rb_load_path); | |
rb_define_readonly_variable("$-I", &rb_load_path); | |
rb_define_readonly_variable("$LOAD_PATH", &rb_load_path); | |
rb_features = ary_new(); | |
rb_define_readonly_variable("$\"", &rb_features); | |
rb_define_global_function("load", f_load, 1); | |
rb_define_global_function("require", f_require, 1); | |
rb_define_global_function("autoload", f_autoload, 2); | |
} | |
static void | |
scope_dup(scope) | |
struct SCOPE *scope; | |
{ | |
ID *tbl; | |
VALUE *vars; | |
if (scope->flag & SCOPE_MALLOC) return; | |
if (scope->local_tbl) { | |
tbl = scope->local_tbl; | |
vars = ALLOC_N(VALUE, tbl[0]+1); | |
*vars++ = scope->local_vars[-1]; | |
MEMCPY(vars, scope->local_vars, VALUE, tbl[0]); | |
scope->local_vars = vars; | |
scope->flag = SCOPE_MALLOC; | |
} | |
else { | |
scope->flag = SCOPE_NOSTACK; | |
} | |
} | |
static void | |
blk_mark(data) | |
struct BLOCK *data; | |
{ | |
gc_mark_frame(&data->frame); | |
gc_mark(data->scope); | |
gc_mark(data->var); | |
gc_mark(data->body); | |
gc_mark(data->self); | |
gc_mark(data->d_vars); | |
} | |
static void | |
blk_free(data) | |
struct BLOCK *data; | |
{ | |
free(data->frame.argv); | |
} | |
static VALUE | |
f_binding(self) | |
VALUE self; | |
{ | |
struct BLOCK *data; | |
VALUE bind; | |
PUSH_BLOCK(0,0); | |
bind = Data_Make_Struct(cData, struct BLOCK, blk_mark, blk_free, data); | |
MEMCPY(data, the_block, struct BLOCK, 1); | |
data->iter = f_iterator_p(); | |
data->frame.last_func = 0; | |
data->frame.argv = ALLOC_N(VALUE, data->frame.argc); | |
MEMCPY(data->frame.argv, the_block->frame.argv, VALUE, data->frame.argc); | |
scope_dup(data->scope); | |
POP_BLOCK(); | |
return bind; | |
} | |
#define PROC_TAINT FL_USER0 | |
#define PROC_T3 FL_USER1 | |
#define PROC_T4 FL_USER2 | |
#define PROC_T5 (FL_USER1|FL_USER2) | |
#define PROC_TMASK (FL_USER1|FL_USER2) | |
static VALUE | |
proc_s_new(class) | |
VALUE class; | |
{ | |
VALUE proc; | |
struct BLOCK *data; | |
if (!iterator_p() && !f_iterator_p()) { | |
ArgError("tryed to create Procedure-Object out of iterator"); | |
} | |
proc = Data_Make_Struct(class, struct BLOCK, blk_mark, blk_free, data); | |
*data = *the_block; | |
#ifdef THREAD | |
data->orig_thread = thread_current(); | |
#endif | |
data->iter = f_iterator_p(); | |
data->frame.argv = ALLOC_N(VALUE, data->frame.argc); | |
MEMCPY(data->frame.argv, the_block->frame.argv, VALUE, data->frame.argc); | |
scope_dup(data->scope); | |
if (safe_level >= 3) { | |
FL_SET(proc, PROC_TAINT); | |
switch (safe_level) { | |
case 3: | |
FL_SET(proc, PROC_T3); | |
break; | |
case 4: | |
FL_SET(proc, PROC_T4); | |
break; | |
case 5: | |
FL_SET(proc, PROC_T5); | |
break; | |
} | |
} | |
return proc; | |
} | |
VALUE | |
f_lambda() | |
{ | |
return proc_s_new(cProc); | |
} | |
static VALUE | |
proc_call(proc, args) | |
VALUE proc, args; | |
{ | |
struct BLOCK *data; | |
volatile VALUE result = Qnil; | |
NODE *state; | |
int tag_level; | |
volatile int orphan; | |
volatile int safe = safe_level; | |
if (TYPE(args) == T_ARRAY) { | |
switch (RARRAY(args)->len) { | |
case 0: | |
args = 0; | |
break; | |
case 1: | |
args = RARRAY(args)->ptr[0]; | |
break; | |
} | |
} | |
Data_Get_Struct(proc, struct BLOCK, data); | |
if (data->scope && (data->scope->flag & SCOPE_NOSTACK)) { | |
orphan = 1; | |
} | |
else { | |
#ifdef THREAD | |
if (data->orig_thread != thread_current()) { | |
orphan = 1; | |
} | |
else | |
#endif | |
orphan = 0; | |
} | |
if (orphan) {/* orphan procedure */ | |
if (iterator_p()) { | |
data->frame.iter = ITER_CUR; | |
} | |
else { | |
data->frame.iter = ITER_NOT; | |
} | |
} | |
/* PUSH BLOCK from data */ | |
PUSH_BLOCK2(data); | |
PUSH_ITER(ITER_CUR); | |
the_frame->iter = ITER_CUR; | |
if (FL_TEST(proc, PROC_TAINT)) { | |
switch (RBASIC(proc)->flags & PROC_TMASK) { | |
case PROC_T3: | |
safe_level = 3; | |
break; | |
case PROC_T4: | |
safe_level = 4; | |
break; | |
case PROC_T5: | |
safe_level = 5; | |
break; | |
} | |
} | |
PUSH_TAG(); | |
state = EXEC_TAG(); | |
if (state == 0) { | |
result = rb_yield(args); | |
} | |
POP_TAG(); | |
POP_ITER(); | |
tag_level = the_block->level; | |
POP_BLOCK(); | |
safe_level = safe; | |
if (state) { | |
if (orphan) {/* orphan procedure */ | |
switch (state->nd_tag) { | |
case TAG_BREAK: /* never happen */ | |
case IN_BLOCK|TAG_BREAK: | |
if (state->nd_tlev == tag_level) | |
Raise(eLocalJumpError, "break from proc-closure"); | |
break; | |
case TAG_RETRY: | |
Raise(eLocalJumpError, "retry from proc-closure"); | |
break; | |
case TAG_RETURN: /* never happen */ | |
case IN_BLOCK|TAG_RETURN: | |
if (state->nd_tlev == tag_level) | |
Raise(eLocalJumpError, "return from proc-closure"); | |
break; | |
} | |
} | |
else if (state->nd_tlev == tag_level) { | |
state->nd_tag &= ~IN_BLOCK; | |
} | |
JUMP_TAG(state); | |
} | |
return result; | |
} | |
void | |
Init_Proc() | |
{ | |
eLocalJumpError = rb_define_class("LocalJumpError", eException); | |
cProc = rb_define_class("Proc", cObject); | |
rb_define_singleton_method(cProc, "new", proc_s_new, 0); | |
rb_define_method(cProc, "call", proc_call, -2); | |
rb_define_global_function("proc", f_lambda, 0); | |
rb_define_global_function("lambda", f_lambda, 0); | |
rb_define_global_function("binding", f_binding, 0); | |
} | |
#ifdef THREAD | |
static VALUE eThreadError; | |
int thread_pending = 0; | |
static VALUE cThread; | |
#include <sys/types.h> | |
#ifdef HAVE_SYS_TIME_H | |
# include <sys/time.h> | |
#else | |
#ifndef NT | |
struct timeval { | |
long tv_sec; /* seconds */ | |
long tv_usec; /* and microseconds */ | |
}; | |
#endif /* NT */ | |
#endif | |
#include <signal.h> | |
#include <errno.h> | |
#ifdef HAVE_SYS_SELECT_H | |
#include <sys/select.h> | |
#endif | |
extern VALUE last_status; | |
enum thread_status { | |
THREAD_RUNNABLE, | |
THREAD_STOPPED, | |
THREAD_TO_KILL, | |
THREAD_KILLED, | |
}; | |
#define WAIT_FD (1<<0) | |
#define WAIT_TIME (1<<1) | |
#define WAIT_JOIN (1<<2) | |
/* +infty, for this purpose */ | |
#define DELAY_INFTY 1E30 | |
typedef struct thread * thread_t; | |
struct thread { | |
struct thread *next, *prev; | |
jmp_buf context; | |
VALUE result; | |
int stk_len; | |
int stk_max; | |
VALUE*stk_ptr; | |
VALUE*stk_pos; | |
struct FRAME *frame; | |
struct SCOPE *scope; | |
struct RClass *class; | |
struct RVarmap *dyna_vars; | |
struct BLOCK *block; | |
struct iter *iter; | |
struct tag *tag; | |
VALUE trace; | |
char *file; | |
int line; | |
VALUE errat, errinfo; | |
VALUE last_status; | |
VALUE last_line; | |
VALUE last_match; | |
int safe; | |
enum thread_status status; | |
int wait_for; | |
int fd; | |
double delay; | |
thread_t join; | |
int abort; | |
VALUE thread; | |
}; | |
static thread_t curr_thread; | |
static int num_waiting_on_fd; | |
static int num_waiting_on_timer; | |
static int num_waiting_on_join; | |
#define FOREACH_THREAD_FROM(f,x) x = f; do { x = x->next; | |
#define END_FOREACH_FROM(f,x) } while (x != f) | |
#define FOREACH_THREAD(x) FOREACH_THREAD_FROM(curr_thread,x) | |
#define END_FOREACH(x) END_FOREACH_FROM(curr_thread,x) | |
/* Return the current time as a floating-point number */ | |
static double | |
timeofday() | |
{ | |
struct timeval tv; | |
gettimeofday(&tv, NULL); | |
return (double)tv.tv_sec + (double)tv.tv_usec * 1e-6; | |
} | |
static thread_t main_thread; | |
#define ADJ(addr) (void*)(((VALUE*)(addr)-th->stk_pos)+th->stk_ptr) | |
#define STACK(addr) (th->stk_pos<(addr) && (addr)<th->stk_pos+th->stk_len) | |
static void | |
thread_mark(th) | |
thread_t th; | |
{ | |
struct FRAME *frame; | |
struct BLOCK *block; | |
gc_mark(th->result); | |
if (th->stk_ptr) { | |
gc_mark_locations(th->stk_ptr, th->stk_ptr+th->stk_len); | |
#if defined(THINK_C) || defined(__human68k__) | |
gc_mark_locations(th->stk_ptr+2, th->stk_ptr+th->stk_len+2); | |
#endif | |
} | |
gc_mark(th->thread); | |
if (th->join) gc_mark(th->join->thread); | |
gc_mark(th->scope); | |
gc_mark(th->dyna_vars); | |
gc_mark(th->errat); | |
gc_mark(th->errinfo); | |
gc_mark(th->last_line); | |
gc_mark(th->last_match); | |
/* mark data in copied stack */ | |
frame = th->frame; | |
while (frame && frame != top_frame) { | |
frame = ADJ(frame); | |
if (frame->argv && !STACK(frame->argv)) { | |
gc_mark_frame(frame); | |
} | |
frame = frame->prev; | |
} | |
block = th->block; | |
while (block) { | |
block = ADJ(block); | |
if (block->frame.argv && !STACK(block->frame.argv)) { | |
gc_mark_frame(&block->frame); | |
} | |
block = block->prev; | |
} | |
} | |
void | |
gc_mark_threads() | |
{ | |
thread_t th; | |
FOREACH_THREAD(th) { | |
thread_mark(th); | |
} END_FOREACH(th); | |
} | |
static void | |
thread_free(th) | |
thread_t th; | |
{ | |
if (th->stk_ptr) free(th->stk_ptr); | |
th->stk_ptr = 0; | |
} | |
static thread_t | |
thread_check(data) | |
struct RData *data; | |
{ | |
if (TYPE(data) != T_DATA || data->dfree != thread_free) { | |
TypeError("wrong argument type %s (expected Thread)", | |
rb_class2name(CLASS_OF(data))); | |
} | |
return (thread_t)data->data; | |
} | |
VALUE lastline_get(); | |
void lastline_set(); | |
VALUE backref_get(); | |
void backref_set(); | |
static void | |
thread_save_context(th) | |
thread_t th; | |
{ | |
VALUE v; | |
th->stk_len = stack_length(); | |
th->stk_pos = (gc_stack_start<(VALUE*)&v)?gc_stack_start | |
:gc_stack_start - th->stk_len; | |
if (th->stk_len > th->stk_max) { | |
th->stk_max = th->stk_len; | |
REALLOC_N(th->stk_ptr, VALUE, th->stk_max); | |
} | |
FLUSH_REGISTER_WINDOWS; | |
MEMCPY(th->stk_ptr, th->stk_pos, VALUE, th->stk_len); | |
th->frame = the_frame; | |
th->scope = the_scope; | |
th->class = the_class; | |
th->dyna_vars = the_dyna_vars; | |
th->block = the_block; | |
th->iter = the_iter; | |
th->tag = prot_tag; | |
th->errat = errat; | |
th->errinfo = errinfo; | |
th->last_status = last_status; | |
th->last_line = lastline_get(); | |
th->last_match = backref_get(); | |
th->safe = safe_level; | |
th->trace = trace_func; | |
th->file = sourcefile; | |
th->line = sourceline; | |
} | |
static void thread_restore_context(); | |
static void | |
stack_extend(th, exit) | |
thread_t th; | |
int exit; | |
{ | |
VALUE space[1024]; | |
memset(space, 0, 1); /* prevent array from optimization */ | |
thread_restore_context(th, exit); | |
} | |
static int th_raise_argc; | |
static VALUE th_raise_argv[2]; | |
static char *th_raise_file; | |
static int th_raise_line; | |
static void | |
thread_restore_context(th, exit) | |
thread_t th; | |
int exit; | |
{ | |
VALUE v; | |
static thread_t tmp; | |
static int ex; | |
if (!th->stk_ptr) Bug("unsaved context"); | |
if (&v < gc_stack_start) { | |
/* Stack grows downward */ | |
if (&v > th->stk_pos) stack_extend(th, exit); | |
} | |
else { | |
/* Stack grows upward */ | |
if (&v < th->stk_pos + th->stk_len) stack_extend(th, exit); | |
} | |
the_frame = th->frame; | |
the_scope = th->scope; | |
the_class = th->class; | |
the_dyna_vars = th->dyna_vars; | |
the_block = th->block; | |
the_iter = th->iter; | |
prot_tag = th->tag; | |
the_class = th->class; | |
errat = th->errat; | |
errinfo = th->errinfo; | |
last_status = th->last_status; | |
safe_level = th->safe; | |
trace_func = th->trace; | |
sourcefile = th->file; | |
sourceline = th->line; | |
tmp = th; | |
ex = exit; | |
FLUSH_REGISTER_WINDOWS; | |
MEMCPY(tmp->stk_pos, tmp->stk_ptr, VALUE, tmp->stk_len); | |
lastline_set(tmp->last_line); | |
backref_set(tmp->last_match); | |
switch (ex) { | |
case 1: | |
JUMP_TAG2(TAG_FATAL, INT2FIX(0)); | |
break; | |
case 2: | |
rb_interrupt(); | |
break; | |
case 3: | |
the_frame->last_func = 0; | |
sourcefile = th_raise_file; | |
sourceline = th_raise_line; | |
f_raise(th_raise_argc, th_raise_argv); | |
break; | |
default: | |
longjmp(tmp->context, 1); | |
} | |
} | |
static void | |
thread_ready(th) | |
thread_t th; | |
{ | |
/* The thread is no longer waiting on anything */ | |
if (th->wait_for & WAIT_FD) { | |
num_waiting_on_fd--; | |
} | |
if (th->wait_for & WAIT_TIME) { | |
num_waiting_on_timer--; | |
} | |
if (th->wait_for & WAIT_JOIN) { | |
num_waiting_on_join--; | |
} | |
th->wait_for = 0; | |
th->status = THREAD_RUNNABLE; | |
} | |
static void | |
thread_remove() | |
{ | |
thread_ready(curr_thread); | |
curr_thread->status = THREAD_KILLED; | |
curr_thread->prev->next = curr_thread->next; | |
curr_thread->next->prev = curr_thread->prev; | |
thread_schedule(); | |
} | |
static int | |
thread_dead(th) | |
thread_t th; | |
{ | |
return th->status == THREAD_KILLED; | |
} | |
static void | |
thread_deadlock() | |
{ | |
curr_thread = main_thread; | |
th_raise_argc = 1; | |
th_raise_argv[0] = exc_new2(eFatal, "Thread: deadlock"); | |
th_raise_file = sourcefile; | |
th_raise_line = sourceline; | |
f_abort(); | |
} | |
void | |
thread_schedule() | |
{ | |
thread_t next; | |
thread_t th; | |
thread_t curr; | |
select_err: | |
thread_pending = 0; | |
if (curr_thread == curr_thread->next) return; | |
next = 0; | |
curr = curr_thread; /* starting thread */ | |
while (curr->status == THREAD_KILLED) { | |
curr = curr->prev; | |
} | |
FOREACH_THREAD_FROM(curr,th) { | |
if (th->status != THREAD_STOPPED && th->status != THREAD_KILLED) { | |
next = th; | |
break; | |
} | |
} | |
END_FOREACH_FROM(curr,th); | |
if (num_waiting_on_join) { | |
curr_thread->file = sourcefile; | |
curr_thread->line = sourceline; | |
FOREACH_THREAD_FROM(curr,th) { | |
if ((th->wait_for & WAIT_JOIN) && thread_dead(th->join)) { | |
th->join = 0; | |
th->wait_for &= ~WAIT_JOIN; | |
th->status = THREAD_RUNNABLE; | |
num_waiting_on_join--; | |
if (!next) next = th; | |
} | |
} | |
END_FOREACH_FROM(curr,th); | |
} | |
if (num_waiting_on_fd > 0 || num_waiting_on_timer > 0) { | |
fd_set readfds; | |
struct timeval delay_tv, *delay_ptr; | |
double delay, now; | |
int n, max; | |
do { | |
max = 0; | |
FD_ZERO(&readfds); | |
if (num_waiting_on_fd > 0) { | |
FOREACH_THREAD_FROM(curr,th) { | |
if (th->wait_for & WAIT_FD) { | |
FD_SET(th->fd, &readfds); | |
if (th->fd > max) max = th->fd; | |
} | |
} | |
END_FOREACH_FROM(curr,th); | |
} | |
delay = DELAY_INFTY; | |
if (num_waiting_on_timer > 0) { | |
now = timeofday(); | |
FOREACH_THREAD_FROM(curr,th) { | |
if (th->wait_for & WAIT_TIME) { | |
if (th->delay <= now) { | |
th->delay = 0.0; | |
th->wait_for &= ~WAIT_TIME; | |
th->status = THREAD_RUNNABLE; | |
num_waiting_on_timer--; | |
next = th; | |
} else if (th->delay < delay) { | |
delay = th->delay; | |
} | |
} | |
} | |
END_FOREACH_FROM(curr,th); | |
} | |
/* Do the select if needed */ | |
if (num_waiting_on_fd > 0 || !next) { | |
/* Convert delay to a timeval */ | |
/* If a thread is runnable, just poll */ | |
if (next) { | |
delay_tv.tv_sec = 0; | |
delay_tv.tv_usec = 0; | |
delay_ptr = &delay_tv; | |
} | |
else if (delay == DELAY_INFTY) { | |
delay_ptr = 0; | |
} | |
else { | |
delay -= now; | |
delay_tv.tv_sec = (unsigned int)delay; | |
delay_tv.tv_usec = (delay - (double)delay_tv.tv_sec) * 1e6; | |
delay_ptr = &delay_tv; | |
} | |
n = select(max+1, &readfds, 0, 0, delay_ptr); | |
if (n < 0) { | |
if (trap_pending) rb_trap_exec(); | |
goto select_err; | |
} | |
if (n > 0) { | |
/* Some descriptors are ready. | |
Make the corresponding threads runnable. */ | |
FOREACH_THREAD_FROM(curr,th) { | |
if ((th->wait_for&WAIT_FD) | |
&& FD_ISSET(th->fd, &readfds)) { | |
/* Wake up only one thread per fd. */ | |
FD_CLR(th->fd, &readfds); | |
th->status = THREAD_RUNNABLE; | |
th->fd = 0; | |
th->wait_for &= ~WAIT_FD; | |
num_waiting_on_fd--; | |
if (!next) next = th; /* Found one. */ | |
} | |
} | |
END_FOREACH_FROM(curr,th); | |
} | |
} | |
/* The delays for some of the threads should have expired. | |
Go through the loop once more, to check the delays. */ | |
} while (!next && delay != DELAY_INFTY); | |
} | |
if (!next) { | |
FOREACH_THREAD_FROM(curr,th) { | |
fprintf(stderr, "%s:%d:deadlock 0x%x: %d:%d %s\n", | |
th->file, th->line, th->thread, th->status, | |
th->wait_for, th==main_thread?"(main)":""); | |
} | |
END_FOREACH_FROM(curr,th); | |
/* raise fatal error to main thread */ | |
thread_deadlock(); | |
} | |
if (next == curr_thread) { | |
return; | |
} | |
/* context switch */ | |
if (curr == curr_thread) { | |
thread_save_context(curr); | |
if (setjmp(curr->context)) { | |
return; | |
} | |
} | |
curr_thread = next; | |
if (next->status == THREAD_TO_KILL) { | |
/* execute ensure-clause if any */ | |
thread_restore_context(next, 1); | |
} | |
thread_restore_context(next, 0); | |
} | |
void | |
thread_wait_fd(fd) | |
int fd; | |
{ | |
if (curr_thread == curr_thread->next) return; | |
curr_thread->status = THREAD_STOPPED; | |
curr_thread->fd = fd; | |
num_waiting_on_fd++; | |
curr_thread->wait_for |= WAIT_FD; | |
thread_schedule(); | |
} | |
void | |
thread_fd_writable(fd) | |
int fd; | |
{ | |
struct timeval zero; | |
fd_set fds; | |
if (curr_thread == curr_thread->next) return; | |
zero.tv_sec = zero.tv_usec = 0; | |
for (;;) { | |
FD_ZERO(&fds); | |
FD_SET(fd, &fds); | |
if (select(fd+1, 0, &fds, 0, &zero) == 1) break; | |
thread_schedule(); | |
} | |
} | |
void | |
thread_wait_for(time) | |
struct timeval time; | |
{ | |
double date; | |
if (curr_thread == curr_thread->next) { | |
int n; | |
#ifndef linux | |
double d, limit; | |
limit = timeofday()+(double)time.tv_sec+(double)time.tv_usec*1e-6; | |
#endif | |
for (;;) { | |
TRAP_BEG; | |
n = select(0, 0, 0, 0, &time); | |
TRAP_END; | |
if (n == 0) return; | |
#ifndef linux | |
d = limit - timeofday(); | |
time.tv_sec = (int)d; | |
time.tv_usec = (int)((d - (int)d)*1e6); | |
if (time.tv_usec < 0) { | |
time.tv_usec += 1e6; | |
time.tv_sec -= 1; | |
} | |
if (time.tv_sec < 0) return; | |
#endif | |
} | |
} | |
date = timeofday() + (double)time.tv_sec + (double)time.tv_usec*1e-6; | |
curr_thread->status = THREAD_STOPPED; | |
curr_thread->delay = date; | |
num_waiting_on_timer++; | |
curr_thread->wait_for |= WAIT_TIME; | |
thread_schedule(); | |
} | |
void thread_sleep_forever(); | |
int | |
thread_alone() | |
{ | |
return curr_thread == curr_thread->next; | |
} | |
int | |
thread_select(max, read, write, except, timeout) | |
int max; | |
fd_set *read, *write, *except; | |
struct timeval *timeout; | |
{ | |
double limit; | |
struct timeval zero; | |
fd_set r, *rp, w, *wp, x, *xp; | |
int n; | |
if (!read && !write && !except) { | |
if (!timeout) { | |
thread_sleep_forever(); | |
return 0; | |
} | |
thread_wait_for(*timeout); | |
return 0; | |
} | |
if (timeout) { | |
limit = timeofday()+ | |
(double)timeout->tv_sec+(double)timeout->tv_usec*1e-6; | |
} | |
if (curr_thread == curr_thread->next) { /* no other thread */ | |
#ifndef linux | |
struct timeval tv, *tvp = timeout; | |
if (timeout) { | |
tv = *timeout; | |
tvp = &tv; | |
} | |
for (;;) { | |
TRAP_BEG; | |
n = select(max, read, write, except, tvp); | |
TRAP_END; | |
if (n < 0 && errno == EINTR) { | |
if (timeout) { | |
double d = timeofday() - limit; | |
tv.tv_sec = (unsigned int)d; | |
tv.tv_usec = (d - (double)tv.tv_sec) * 1e6; | |
} | |
continue; | |
} | |
return n; | |
} | |
#else | |
for (;;) { | |
TRAP_BEG; | |
n = select(max, read, write, except, timeout); | |
TRAP_END; | |
if (n < 0 && errno == EINTR) { | |
continue; | |
} | |
return n; | |
} | |
#endif | |
} | |
for (;;) { | |
zero.tv_sec = zero.tv_usec = 0; | |
if (read) {rp = &r; r = *read;} else {rp = 0;} | |
if (write) {wp = &w; w = *write;} else {wp = 0;} | |
if (except) {xp = &x; x = *except;} else {xp = 0;} | |
n = select(max, rp, wp, xp, &zero); | |
if (n > 0) { | |
/* write back fds */ | |
if (read) {*read = r;} | |
if (write) {*write = w;} | |
if (except) {*except = x;} | |
return n; | |
} | |
if (n < 0 && errno != EINTR) { | |
return n; | |
} | |
if (timeout) { | |
if (timeout->tv_sec == 0 && timeout->tv_usec == 0) return 0; | |
if (limit <= timeofday()) return 0; | |
} | |
thread_schedule(); | |
CHECK_INTS; | |
} | |
} | |
static VALUE | |
thread_join(dmy, thread) | |
VALUE dmy; | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
if (thread_dead(th)) return thread; | |
if ((th->wait_for & WAIT_JOIN) && th->join == curr_thread) | |
Raise(eThreadError, "Thread.join: deadlock"); | |
curr_thread->status = THREAD_STOPPED; | |
curr_thread->join = th; | |
num_waiting_on_join++; | |
curr_thread->wait_for |= WAIT_JOIN; | |
thread_schedule(); | |
return thread; | |
} | |
static VALUE | |
thread_current() | |
{ | |
return curr_thread->thread; | |
} | |
static VALUE | |
thread_main() | |
{ | |
return main_thread->thread; | |
} | |
static VALUE | |
thread_wakeup(thread) | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
if (th->status == THREAD_KILLED) Raise(eThreadError, "killed thread"); | |
thread_ready(th); | |
return thread; | |
} | |
static VALUE | |
thread_run(thread) | |
VALUE thread; | |
{ | |
thread_wakeup(thread); | |
if (!thread_critical) thread_schedule(); | |
return thread; | |
} | |
static VALUE | |
thread_kill(thread) | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
if (th->status == THREAD_TO_KILL || th->status == THREAD_KILLED) | |
return thread; | |
if (th == th->next || th == main_thread) rb_exit(0); | |
thread_ready(th); | |
th->status = THREAD_TO_KILL; | |
thread_schedule(); | |
return Qnil; /* not reached */ | |
} | |
static VALUE | |
thread_s_kill(obj, th) | |
VALUE obj, th; | |
{ | |
return thread_kill(th); | |
} | |
static VALUE | |
thread_exit() | |
{ | |
return thread_kill(curr_thread->thread); | |
} | |
static VALUE | |
thread_pass() | |
{ | |
thread_schedule(); | |
return Qnil; | |
} | |
static VALUE | |
thread_stop_method(thread) | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
thread_critical = 0; | |
th->status = THREAD_STOPPED; | |
thread_schedule(); | |
return thread; | |
} | |
static VALUE | |
thread_stop() | |
{ | |
thread_stop_method(curr_thread->thread); | |
return Qnil; | |
} | |
void | |
thread_sleep(sec) | |
int sec; | |
{ | |
if (curr_thread == curr_thread->next) { | |
TRAP_BEG; | |
sleep(sec); | |
TRAP_END; | |
return; | |
} | |
thread_wait_for(time_timeval(INT2FIX(sec))); | |
} | |
void | |
thread_sleep_forever() | |
{ | |
if (curr_thread == curr_thread->next) { | |
TRAP_BEG; | |
sleep((32767<<16)+32767); | |
TRAP_END; | |
return; | |
} | |
num_waiting_on_timer++; | |
curr_thread->delay = DELAY_INFTY; | |
curr_thread->wait_for |= WAIT_TIME; | |
curr_thread->status = THREAD_STOPPED; | |
thread_schedule(); | |
} | |
static int thread_abort; | |
static VALUE | |
thread_s_abort_exc() | |
{ | |
return thread_abort?TRUE:FALSE; | |
} | |
static VALUE | |
thread_s_abort_exc_set(self, val) | |
VALUE self, val; | |
{ | |
thread_abort = RTEST(val); | |
return val; | |
} | |
static VALUE | |
thread_abort_exc(thread) | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
return th->abort?TRUE:FALSE; | |
} | |
static VALUE | |
thread_abort_exc_set(thread, val) | |
VALUE thread, val; | |
{ | |
thread_t th = thread_check(thread); | |
th->abort = RTEST(val); | |
return val; | |
} | |
static thread_t | |
thread_alloc() | |
{ | |
thread_t th; | |
th = ALLOC(struct thread); | |
th->status = THREAD_RUNNABLE; | |
th->status = 0; | |
th->result = 0; | |
th->errinfo = Qnil; | |
th->errat = Qnil; | |
th->stk_ptr = 0; | |
th->stk_len = 0; | |
th->stk_max = 0; | |
th->wait_for = 0; | |
th->fd = 0; | |
th->delay = 0.0; | |
th->join = 0; | |
th->frame = 0; | |
th->scope = 0; | |
th->class = 0; | |
th->dyna_vars = 0; | |
th->block = 0; | |
th->iter = 0; | |
th->tag = 0; | |
th->errat = 0; | |
th->errinfo = 0; | |
th->last_status = 0; | |
th->last_line = 0; | |
th->last_match = 0; | |
th->abort = 0; | |
th->thread = data_object_alloc(cThread, th, 0, thread_free); | |
if (curr_thread) { | |
th->prev = curr_thread; | |
curr_thread->next->prev = th; | |
th->next = curr_thread->next; | |
curr_thread->next = th; | |
} | |
else { | |
curr_thread = th->prev = th->next = th; | |
th->status = THREAD_RUNNABLE; | |
} | |
return th; | |
} | |
#if defined(HAVE_SETITIMER) && !defined(__BOW__) | |
static void | |
catch_timer(sig) | |
int sig; | |
{ | |
#if !defined(POSIX_SIGNAL) && !defined(BSD_SIGNAL) | |
signal(sig, catch_timer); | |
#endif | |
if (!thread_critical) { | |
if (trap_immediate) { | |
trap_immediate = 0; | |
thread_schedule(); | |
} | |
else thread_pending = 1; | |
} | |
} | |
#else | |
int thread_tick = THREAD_TICK; | |
#endif | |
VALUE | |
thread_create(fn, arg) | |
VALUE (*fn)(); | |
void *arg; | |
{ | |
thread_t th = thread_alloc(); | |
NODE *state; | |
#if defined(HAVE_SETITIMER) && !defined(__BOW__) | |
static init = 0; | |
if (!init) { | |
struct itimerval tval; | |
#ifdef POSIX_SIGNAL | |
posix_signal(SIGVTALRM, catch_timer); | |
#else | |
signal(SIGVTALRM, catch_timer); | |
#endif | |
tval.it_interval.tv_sec = 0; | |
tval.it_interval.tv_usec = 100000; | |
tval.it_value = tval.it_interval; | |
setitimer(ITIMER_VIRTUAL, &tval, NULL); | |
init = 1; | |
} | |
#endif | |
thread_save_context(curr_thread); | |
if (setjmp(curr_thread->context)) { | |
return th->thread; | |
} | |
PUSH_TAG(); | |
if ((state = EXEC_TAG()) == 0) { | |
thread_save_context(th); | |
if (setjmp(th->context) == 0) { | |
curr_thread = th; | |
th->result = (*fn)(arg, th); | |
} | |
} | |
POP_TAG(); | |
if (state) { | |
if (state->nd_tag == TAG_THROW) { | |
char *mesg; | |
char *tag = rb_id2name(state->nd_tlev); | |
mesg = ALLOCA_N(char, strlen(tag) + 64); | |
sprintf(mesg, "uncaught throw `%s' in thread 0x%x\n", | |
tag, th->thread); | |
curr_thread->errinfo = exc_new2(eThreadError, mesg); | |
curr_thread->errat = make_backtrace(); | |
} | |
else if (th->status != THREAD_TO_KILL && !NIL_P(errinfo)) { | |
if (state->nd_tag == TAG_FATAL || | |
obj_is_kind_of(errinfo, eSystemExit)) { | |
/* fatal error or global exit within this thread */ | |
/* need to stop whole script */ | |
main_thread->errat = errat; | |
main_thread->errinfo = errinfo; | |
thread_cleanup(); | |
} | |
else if (thread_abort || curr_thread->abort) { | |
f_abort(); | |
} | |
else { | |
curr_thread->errat = errat; | |
curr_thread->errinfo = errinfo; | |
} | |
} | |
} | |
thread_remove(); | |
return 0; | |
} | |
static void | |
thread_yield(arg, th) | |
int arg; | |
thread_t th; | |
{ | |
scope_dup(the_block->scope); | |
rb_yield(th->thread); | |
} | |
static VALUE | |
thread_start() | |
{ | |
if (!iterator_p()) { | |
Raise(eThreadError, "must be called as iterator"); | |
} | |
return thread_create(thread_yield, 0); | |
} | |
static VALUE | |
thread_value(thread) | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
thread_join(0, thread); | |
if (!NIL_P(th->errinfo)) { | |
errat = make_backtrace(); | |
ary_unshift(errat, ary_entry(th->errat, 0)); | |
sourcefile = 0; /* kludge to print errat */ | |
rb_raise(th->errinfo); | |
} | |
return th->result; | |
} | |
static VALUE | |
thread_status(thread) | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
if (thread_dead(th)) { | |
if (NIL_P(th->errinfo)) return FALSE; | |
return Qnil; | |
} | |
return TRUE; | |
} | |
static VALUE | |
thread_stopped(thread) | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
if (thread_dead(th)) return TRUE; | |
if (th->status == THREAD_STOPPED) return TRUE; | |
return FALSE; | |
} | |
static void | |
thread_wait_other_threads() | |
{ | |
/* wait other threads to terminate */ | |
while (curr_thread != curr_thread->next) { | |
thread_schedule(); | |
} | |
} | |
static void | |
thread_cleanup() | |
{ | |
thread_t th; | |
if (curr_thread != curr_thread->next->prev) { | |
curr_thread = curr_thread->prev; | |
} | |
FOREACH_THREAD(th) { | |
if (th != curr_thread && th->status != THREAD_KILLED) { | |
th->status = THREAD_TO_KILL; | |
th->wait_for = 0; | |
} | |
} | |
END_FOREACH(th); | |
} | |
int thread_critical; | |
static VALUE | |
thread_get_critical() | |
{ | |
return thread_critical?TRUE:FALSE; | |
} | |
static VALUE | |
thread_set_critical(obj, val) | |
VALUE obj, val; | |
{ | |
thread_critical = RTEST(val); | |
return val; | |
} | |
void | |
thread_interrupt() | |
{ | |
thread_critical = 0; | |
thread_ready(main_thread); | |
if (curr_thread == main_thread) { | |
rb_interrupt(); | |
} | |
thread_save_context(curr_thread); | |
if (setjmp(curr_thread->context)) { | |
return; | |
} | |
curr_thread = main_thread; | |
thread_restore_context(curr_thread, 2); | |
} | |
static VALUE | |
thread_raise(argc, argv, thread) | |
int argc; | |
VALUE *argv; | |
VALUE thread; | |
{ | |
thread_t th = thread_check(thread); | |
if (thread_dead(th)) return thread; | |
if (curr_thread == th) { | |
f_raise(argc, argv); | |
} | |
thread_save_context(curr_thread); | |
if (setjmp(curr_thread->context)) { | |
return thread; | |
} | |
rb_scan_args(argc, argv, "11", &th_raise_argv[0], &th_raise_argv[1]); | |
thread_ready(th); | |
curr_thread = th; | |
th_raise_argc = argc; | |
th_raise_file = sourcefile; | |
th_raise_line = sourceline; | |
thread_restore_context(curr_thread, 3); | |
return Qnil; /* not reached */ | |
} | |
static thread_t loading_thread; | |
static int loading_nest; | |
static int | |
thread_loading(feature) | |
char *feature; | |
{ | |
if (curr_thread != curr_thread->next && loading_thread) { | |
while (loading_thread != curr_thread) { | |
thread_schedule(); | |
CHECK_INTS; | |
} | |
if (rb_provided(feature)) return TRUE; /* no need to load */ | |
} | |
loading_thread = curr_thread; | |
loading_nest++; | |
return FALSE; | |
} | |
static void | |
thread_loading_done() | |
{ | |
if (--loading_nest == 0) { | |
loading_thread = 0; | |
} | |
} | |
void | |
Init_Thread() | |
{ | |
eThreadError = rb_define_class("ThreadError", eException); | |
cThread = rb_define_class("Thread", cObject); | |
rb_define_singleton_method(cThread, "new", thread_start, 0); | |
rb_define_singleton_method(cThread, "start", thread_start, 0); | |
rb_define_singleton_method(cThread, "fork", thread_start, 0); | |
rb_define_singleton_method(cThread, "stop", thread_stop, 0); | |
rb_define_singleton_method(cThread, "kill", thread_s_kill, 1); | |
rb_define_singleton_method(cThread, "exit", thread_exit, 0); | |
rb_define_singleton_method(cThread, "pass", thread_pass, 0); | |
rb_define_singleton_method(cThread, "join", thread_join, 1); | |
rb_define_singleton_method(cThread, "current", thread_current, 0); | |
rb_define_singleton_method(cThread, "main", thread_main, 0); | |
rb_define_singleton_method(cThread, "critical", thread_get_critical, 0); | |
rb_define_singleton_method(cThread, "critical=", thread_set_critical, 1); | |
rb_define_singleton_method(cThread, "abort_on_exception", thread_s_abort_exc, 0); | |
rb_define_singleton_method(cThread, "abort_on_exception=", thread_s_abort_exc_set, 1); | |
rb_define_method(cThread, "run", thread_run, 0); | |
rb_define_method(cThread, "wakeup", thread_wakeup, 0); | |
rb_define_method(cThread, "stop", thread_stop_method, 0); | |
rb_define_method(cThread, "exit", thread_kill, 0); | |
rb_define_method(cThread, "value", thread_value, 0); | |
rb_define_method(cThread, "status", thread_status, 0); | |
rb_define_method(cThread, "alive?", thread_status, 0); | |
rb_define_method(cThread, "stop?", thread_stopped, 0); | |
rb_define_method(cThread, "raise", thread_raise, -1); | |
rb_define_method(cThread, "abort_on_exception", thread_abort_exc, 0); | |
rb_define_method(cThread, "abort_on_exception=", thread_abort_exc_set, 1); | |
/* allocate main thread */ | |
main_thread = thread_alloc(); | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment