/************************************************

  eval.c -

  $Author$
  $Date$
  created at: Thu Jun 10 14:22:17 JST 1993

  Copyright (C) 1993-1998 Yukihiro Matsumoto

************************************************/

#include "ruby.h"
#include "node.h"
#include "env.h"
#include "rubysig.h"

#include <stdio.h>
#include <setjmp.h>
#include "st.h"
#include "dln.h"

#ifndef HAVE_STRING_H
char *strrchr _((char*,char));
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef __BEOS__
#include <net/socket.h>
#endif

#ifdef USE_CWGUSI
#include <sys/stat.h>
#include <sys/errno.h>
#include <compat.h>
#endif

#ifndef setjmp
#ifdef HAVE__SETJMP
#define setjmp(env) _setjmp(env)
#define longjmp(env,val) _longjmp(env,val)
#endif
#endif

#if defined(MSDOS) || defined(NT) || defined(__human68k__) || defined(__MACOS__)
#define RUBY_LIB_SEP ";"
#else
#define RUBY_LIB_SEP ":"
#endif

VALUE cProc;
static VALUE cBinding;
static VALUE proc_call _((VALUE,VALUE));
static VALUE f_binding _((VALUE));
static void f_END _((void));
static VALUE f_iterator_p _((void));
static VALUE block_pass _((VALUE,NODE*));
static VALUE cMethod;
static VALUE method_proc _((VALUE));

static int scope_vmode;
#define SCOPE_PUBLIC    0
#define SCOPE_PRIVATE   1
#define SCOPE_PROTECTED 2
#define SCOPE_MODFUNC   5
#define SCOPE_MASK      7
#define SCOPE_SET(f)  do {scope_vmode=(f);} while(0)
#define SCOPE_TEST(f) (scope_vmode&(f))

#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 */
    VALUE klass;		/* receiver's class */
    VALUE 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++;
    }
}

static void
rb_clear_cache_by_id(id)
    ID id;
{
    struct cache_entry *ent, *end;

    ent = cache; end = ent + CACHE_SIZE;
    while (ent < end) {
	if (ent->mid == id) {
	    ent->mid = 0;
	}
	ent++;
    }
}

void
rb_add_method(klass, mid, node, noex)
    VALUE klass;
    ID mid;
    NODE *node;
    int noex;
{
    NODE *body;

    if (NIL_P(klass)) klass = cObject;
    body = NEW_METHOD(node, noex);
    st_insert(RCLASS(klass)->m_tbl, mid, body);
}

static NODE*
search_method(klass, id, origin)
    VALUE klass, *origin;
    ID id;
{
    NODE *body;

    while (!st_lookup(RCLASS(klass)->m_tbl, id, &body)) {
	klass = RCLASS(klass)->super;
	if (!klass) return 0;
    }

    if (origin) *origin = klass;
    return body;
}

static NODE*
rb_get_method_body(klassp, idp, noexp)
    VALUE *klassp;
    ID *idp;
    int *noexp;
{
    ID id = *idp;
    VALUE klass = *klassp;
    VALUE origin;
    NODE *body;
    struct cache_entry *ent;

    if ((body = search_method(klass, id, &origin)) == 0) {
	return 0;
    }
    if (!body->nd_body) return 0;

    /* store in cache */
    ent = cache + EXPR1(klass, id);
    ent->klass  = klass;
    ent->noex   = body->nd_noex;
    body = body->nd_body;
    if (nd_type(body) == NODE_FBODY) {
	ent->mid = id;
	*klassp = body->nd_orig;
	ent->origin = body->nd_orig;
	*idp = ent->mid0 = body->nd_mid;
	body = ent->method = body->nd_head;
    }
    else {
	*klassp = origin;
	ent->origin = origin;
	ent->mid = ent->mid0 = id;
	ent->method = body;
    }

    if (noexp) *noexp = ent->noex;
    return body;
}

void
rb_alias(klass, name, def)
    VALUE klass;
    ID name, def;
{
    VALUE origin;
    NODE *orig, *body;

    if (name == def) return;
    orig = search_method(klass, def, &origin);
    if (!orig || !orig->nd_body) {
	if (TYPE(klass) == T_MODULE) {
	    orig = search_method(cObject, def, &origin);
	}
    }
    if (!orig || !orig->nd_body) {
	NameError("undefined method `%s' for `%s'",
		  rb_id2name(def), rb_class2name(klass));
    }
    body = orig->nd_body;
    if (nd_type(body) == NODE_FBODY) { /* was alias */
	body = body->nd_head;
	def = body->nd_mid;
	origin = body->nd_orig;
    }

    st_insert(RCLASS(klass)->m_tbl, name,
	      NEW_METHOD(NEW_FBODY(body, def, origin), orig->nd_noex));
}

static void
remove_method(klass, mid)
    VALUE klass;
    ID mid;
{
    NODE *body;

    if (!st_delete(RCLASS(klass)->m_tbl, &mid, &body)) {
	NameError("method `%s' not defined in %s",
		  rb_id2name(mid), rb_class2name(klass));
    }
    rb_clear_cache_by_id(mid);
}

void
rb_remove_method(klass, name)
    VALUE klass;
    char *name;
{
    remove_method(klass, rb_intern(name));
}

void
rb_disable_super(klass, name)
    VALUE klass;
    char *name;
{
    VALUE origin;
    NODE *body;
    ID mid = rb_intern(name);

    body = search_method(klass, mid, &origin);
    if (!body || !body->nd_body) {
	NameError("undefined method `%s' for `%s'",
		  rb_id2name(mid), rb_class2name(klass));
    }
    if (origin == klass) {
	body->nd_noex |= NOEX_UNDEF;
    }
    else {
	rb_clear_cache_by_id(mid);
	rb_add_method(the_class, mid, 0, NOEX_UNDEF);
    }
}

void
rb_enable_super(klass, name)
    VALUE klass;
    char *name;
{
    VALUE origin;
    NODE *body;
    ID mid = rb_intern(name);

    body = search_method(klass, mid, &origin);
    if (!body || !body->nd_body || origin != klass) {
	NameError("undefined method `%s' for `%s'",
		  rb_id2name(mid), rb_class2name(klass));
    }
    body->nd_noex &= ~NOEX_UNDEF;
}

static void
rb_export_method(klass, name, noex)
    VALUE klass;
    ID name;
    int noex;
{
    NODE *body;
    VALUE origin;

    body = search_method(klass, name, &origin);
    if (!body && TYPE(klass) == T_MODULE) {
	body = search_method(cObject, name, &origin);
    }
    if (!body) {
	NameError("undefined method `%s' for `%s'",
		  rb_id2name(name), rb_class2name(klass));
    }
    if (body->nd_noex != noex) {
	if (klass == origin) {
	    body->nd_noex = noex;
	}
	else {
	    rb_clear_cache_by_id(name);
	    rb_add_method(klass, name, NEW_ZSUPER(), noex);
	}
    }
}

int
rb_method_boundp(klass, id, ex)
    VALUE klass;
    ID id;
    int ex;
{
    int noex;

    if (rb_get_method_body(&klass, &id, &noex)) {
	if (ex && noex & NOEX_PRIVATE)
	    return FALSE;
	return TRUE;
    }
    return FALSE;
}

void
rb_attr(klass, id, read, write, ex)
    VALUE klass;
    ID id;
    int read, write, ex;
{
    char *name;
    char *buf;
    ID attr, attriv;
    int noex;

    if (!ex) noex = NOEX_PUBLIC;
    else {
	if (SCOPE_TEST(SCOPE_PRIVATE)) {
	    noex = NOEX_PRIVATE;
	    Warning("private attribute?");
	}
	else if (SCOPE_TEST(SCOPE_PROTECTED)) {
	    noex = NOEX_PROTECTED;
	}
	else {
	    noex = NOEX_PUBLIC;
	}
    }

    name = rb_id2name(id);
    if (!name) {
	ArgError("argument needs to be symbol or string");
    }
    buf = ALLOCA_N(char,strlen(name)+2);
    sprintf(buf, "@%s", name);
    attriv = rb_intern(buf);
    if (read) {
	rb_add_method(klass, id, NEW_IVAR(attriv), noex);
    }
    sprintf(buf, "%s=", name);
    id = rb_intern(buf);
    if (write) {
	rb_add_method(klass, id, NEW_ATTRSET(attriv), noex);
    }
}

static ID init, eqq, each, aref, aset, match;
VALUE errinfo = Qnil;
extern NODE *eval_tree_begin;
extern NODE *eval_tree;
extern int nerrs;

static VALUE eLocalJumpError;
static VALUE eSysStackError;

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;
    VALUE klass;
    struct tag *tag;
    int iter;
    int vmode;
    struct RVarmap *d_vars;
#ifdef THREAD
    VALUE orig_thread;
#endif
    struct BLOCK *prev;
};
static struct BLOCK *the_block;

#define PUSH_BLOCK(v,b) {		\
    struct BLOCK _block;		\
    _block.tag = prot_tag;		\
    _block.var = v;			\
    _block.body = b;			\
    _block.self = self;			\
    _block.frame = *the_frame;		\
    _block.klass = 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;	\
    _block.vmode = scope_vmode;		\
    the_block = &_block;

#define POP_BLOCK() 			\
   the_block = _block.prev; 		\
}

struct RVarmap *the_dyna_vars;
#define PUSH_VARS() {			\
    struct RVarmap * volatile _old;	\
    _old = the_dyna_vars;		\
    the_dyna_vars = 0;

#define POP_VARS()			\
    the_dyna_vars = _old;		\
}

static struct RVarmap*
new_dvar(id, value)
    ID id;
    VALUE value;
{
    NEWOBJ(vars, struct RVarmap);
    OBJSETUP(vars, 0, T_VARMAP);
    vars->id = id;
    vars->val = value;
    vars->next = the_dyna_vars;

    return vars;
}

static void
mark_dvar(vars)
    struct RVarmap *vars;
{
    the_dyna_vars = new_dvar(0, 0);
    the_dyna_vars->next = vars;
}

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;
}

void
dyna_var_push(id, value)
    ID id;
    VALUE value;
{
    the_dyna_vars = new_dvar(id, value);
}

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;
    }
    dyna_var_push(id, value);
    return value;
}

static void
dvar_asgn_push(id, value)
    ID id;
    VALUE value;
{
    if (the_dyna_vars && the_dyna_vars->id == 0) {
	struct RVarmap* vars = new_dvar(id, value);

	vars->next = the_dyna_vars->next;
	the_dyna_vars->next = vars;
    }
    else {
	dyna_var_asgn(id, value);
    }
}

struct iter {
    int iter;
    struct iter *prev;
};
static struct iter *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;		\
}

struct tag {
    jmp_buf buf;
    struct FRAME *frame;
    struct iter *iter;
    ID tag;
    VALUE retval;
    ID dst;
    struct tag *prev;
};
static struct tag *prot_tag;

#define PUSH_TAG(ptag) {		\
    struct tag _tag;			\
    _tag.retval = Qnil;			\
    _tag.frame = the_frame;		\
    _tag.iter = the_iter;		\
    _tag.prev = prot_tag;		\
    _tag.retval = Qnil;			\
    _tag.tag = ptag;			\
    _tag.dst = 0;			\
    prot_tag = &_tag;

#define PROT_NONE   0
#define PROT_FUNC   -1
#define PROT_THREAD -2

#define EXEC_TAG()    setjmp(prot_tag->buf)

#define JUMP_TAG(st) {			\
    the_frame = prot_tag->frame;	\
    the_iter = prot_tag->iter;		\
    longjmp(prot_tag->buf,(st));	\
}

#define POP_TAG()			\
    if (_tag.prev)			\
        _tag.prev->retval = _tag.retval;\
    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 TAG_MASK	0xf

VALUE the_class;

#define PUSH_CLASS() {		\
    VALUE _class = the_class;	\

#define POP_CLASS() the_class = _class; }

#define PUSH_SCOPE() {			\
    int volatile _vmode = scope_vmode;	\
    struct SCOPE * volatile _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;			\
    scope_vmode = SCOPE_PUBLIC;

#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;\
    scope_vmode = _vmode;\
}

static VALUE rb_eval _((VALUE,NODE*));
static VALUE eval _((VALUE,VALUE,VALUE,char*,int));
static NODE *compile _((VALUE,char*));

static VALUE rb_call _((VALUE,VALUE,ID,int,VALUE*,int));
static VALUE module_setup _((VALUE,NODE*));

static VALUE massign _((VALUE,NODE*,VALUE));
static void assign _((VALUE,NODE*,VALUE));

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 (str_tainted(x)) {
	if (safe_level > 0){
	    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 _((char*,char*,int,VALUE,ID));

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 VALUE
get_backtrace(info)
    VALUE info;
{
    if (NIL_P(info)) return Qnil;
    return rb_funcall(info, rb_intern("backtrace"), 0);
}

static void
set_backtrace(info, bt)
    VALUE info, bt;
{
    rb_funcall(info, rb_intern("set_backtrace"), 1, bt);
}

static void
error_print()
{
    VALUE errat;
    VALUE eclass;
    VALUE einfo;

    if (NIL_P(errinfo)) return;

    errat = get_backtrace(errinfo);
    if (!NIL_P(errat)) {
	VALUE mesg = RARRAY(errat)->ptr[0];

	if (NIL_P(mesg)) error_pos();
	else {
	    fwrite(RSTRING(mesg)->ptr, 1, RSTRING(mesg)->len, stderr);
	}
    }

    eclass = CLASS_OF(errinfo);
    einfo = obj_as_string(errinfo);
    if (eclass == eRuntimeError && RSTRING(einfo)->len == 0) {
	fprintf(stderr, ": unhandled exception\n");
    }
    else {
	VALUE epath;

	epath = rb_class_path(eclass);
	if (RSTRING(einfo)->len == 0) {
	    fprintf(stderr, ": ");
	    fwrite(RSTRING(epath)->ptr, 1, RSTRING(epath)->len, stderr);
	    putc('\n', stderr);
	}
	else {
	    char *tail  = 0;
	    int len = RSTRING(einfo)->len;

	    if (RSTRING(epath)->ptr[0] == '#') epath = 0;
	    if (tail = strchr(RSTRING(einfo)->ptr, '\n')) {
		len = tail - RSTRING(einfo)->ptr;
		tail++;		/* skip newline */
	    }
	    fprintf(stderr, ": ");
	    fwrite(RSTRING(einfo)->ptr, 1, len, stderr);
	    if (epath) {
		fprintf(stderr, " (");
		fwrite(RSTRING(epath)->ptr, 1, RSTRING(epath)->len, stderr);
		fprintf(stderr, ")\n");
	    }
	    if (tail) {
		fwrite(tail, 1, RSTRING(einfo)->len-len-1, stderr);
		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

	ep = RARRAY(errat);
	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;
	    }
	}
    }
}

#if !defined(NT) && !defined(__MACOS__)
extern char **environ;
#endif
char **origenviron;

void rb_call_inits _((void));
void init_stack _((void));
void init_heap _((void));
void Init_ext _((void));
void gc_call_finalizer_at_exit _((void));

void
ruby_init()
{
    static struct FRAME frame;
    static struct iter iter;
    int state;

    the_frame = top_frame = &frame;
    the_iter = &iter;

#ifdef __MACOS__
    origenviron = 0;
#else
    origenviron = environ;
#endif

    init_heap();
    PUSH_SCOPE();
    the_scope->local_vars = 0;
    the_scope->local_tbl  = 0;
    top_scope = the_scope;
    /* default visibility is private at toplevel */
    SCOPE_SET(SCOPE_PRIVATE);

    PUSH_TAG(PROT_NONE)
    if ((state = EXEC_TAG()) == 0) {
	rb_call_inits();
	the_class = cObject;
	the_frame->self = TopSelf;
	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;
{
    int state;

    PUSH_TAG(PROT_NONE)
    if ((state = EXEC_TAG()) == 0) {
	NODE *save;

	ruby_process_options(argc, argv);
	ext_init = 1;	/* Init_ext() called in ruby_process_options */
	save = eval_tree;
	ruby_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_begin) {
	tree = eval_tree_begin;
	eval_tree_begin = 0;
	rb_eval(self, 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 _((void));
static void thread_wait_other_threads _((void));
static VALUE thread_current _((void));
#endif

static int exit_status;

static void exec_end_proc _((void));

void
ruby_run()
{
    int state;
    static int ex;

    if (nerrs > 0) exit(nerrs);

    init_stack();

    PUSH_TAG(PROT_NONE);
    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(PROT_NONE);
    PUSH_ITER(ITER_NOT);
    if ((state = EXEC_TAG()) == 0) {
	rb_trap_exit();
#ifdef THREAD
	thread_cleanup();
	thread_wait_other_threads();
#endif
	exec_end_proc();
	gc_call_finalizer_at_exit();
    }
    else {
	ex = state;
    }
    POP_ITER();
    POP_TAG();

    switch (ex & 0xf) {
      case 0:
	exit(0);

      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 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;
      default:
	Bug("Unknown longjmp status %d", ex);
	break;
    }
}

static void
compile_error(at)
    char *at;
{
    VALUE str;
    char *mesg;
    int len;

    mesg = str2cstr(errinfo, &len);
    nerrs = 0;
    str = str_new2("compile error");
    if (at) {
	str_cat(str, " in ", 4);
	str_cat(str, at, strlen(at));
    }
    str_cat(str, "\n", 1);
    str_cat(str, mesg, len);
    rb_raise(exc_new3(eSyntaxError, str));
}

VALUE
rb_eval_string(str)
    char *str;
{
    VALUE v;
    char *oldsrc = sourcefile;

    sourcefile = "(eval)";
    v = eval(TopSelf, str_new2(str), Qnil, 0, 0);
    sourcefile = oldsrc;

    return v;
}

VALUE
rb_eval_cmd(cmd, arg)
    VALUE cmd, arg;
{
    int state;
    VALUE val;
    struct SCOPE *saved_scope;
    volatile int safe = rb_safe_level();

    if (TYPE(cmd) != T_STRING) {
	Check_Type(arg, T_ARRAY);
	return rb_funcall2(cmd, rb_intern("call"),
			   RARRAY(arg)->len, RARRAY(arg)->ptr);
    }

    PUSH_CLASS();
    PUSH_TAG(PROT_NONE);
    saved_scope = the_scope;
    the_scope = top_scope;

    the_class = cObject;
    if (str_tainted(cmd)) {
	safe_level = 5;
    }

    if ((state = EXEC_TAG()) == 0) {
	val = eval(TopSelf, cmd, Qnil, 0, 0);
    }

    the_scope = saved_scope;
    safe_level = safe;
    POP_TAG();
    POP_CLASS();

    switch (state) {
      case 0:
	break;
      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;
    }
    return val;
}

VALUE
rb_trap_eval(cmd, sig)
    VALUE cmd;
    int sig;
{
    int state;
    VALUE val;			/* OK */

    PUSH_TAG(PROT_NONE);
    if ((state = EXEC_TAG()) == 0) {
	val = rb_eval_cmd(cmd, ary_new3(1, INT2FIX(sig)));
    }
    POP_TAG();
    if (state) {
	trap_immediate = 0;
	JUMP_TAG(state);
    }
    return val;
}

static VALUE
superclass(self, node)
    VALUE self;
    NODE *node;
{
    VALUE val;			/* OK */
    int state;

    PUSH_TAG(PROT_NONE);
    if ((state = EXEC_TAG()) == 0) {
	val = rb_eval(self, node);
    }
    POP_TAG();
    if (state) {
      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 *klass = RCLASS(cbase->nd_clss);

	if (klass->iv_tbl &&
	    st_lookup(klass->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 *klass = RCLASS(cbase->nd_clss);

	if (klass->iv_tbl &&
	    st_lookup(klass->iv_tbl, id, &result)) {
	    return result;
	}
	cbase = cbase->nd_next;
    }
    return rb_const_get(cref->nd_clss, id);
}

static VALUE
mod_nesting()
{
    NODE *cbase = (NODE*)the_frame->cbase;
    VALUE ary = ary_new();

    while (cbase && cbase->nd_clss != cObject) {
	ary_push(ary, cbase->nd_clss);
	cbase = cbase->nd_next;
    }
    return ary;
}

static VALUE
mod_s_constants()
{
    NODE *cbase = (NODE*)the_frame->cbase;
    VALUE ary = ary_new();

    while (cbase && cbase->nd_clss != cObject) {
	mod_const_at(cbase->nd_clss, ary);
	cbase = cbase->nd_next;
    }

    mod_const_of(((NODE*)the_frame->cbase)->nd_clss, ary);
    return ary;
}

static VALUE
mod_remove_method(mod, name)
    VALUE mod, name;
{
    remove_method(mod, rb_to_id(name));
    return mod;
}

static VALUE
mod_undef_method(mod, name)
    VALUE mod, name;
{
    ID id = rb_to_id(name);

    rb_add_method(mod, id, 0, NOEX_PUBLIC);
    rb_clear_cache_by_id(id);
    return mod;
}

static VALUE
mod_alias_method(mod, newname, oldname)
    VALUE mod, newname, oldname;
{
    ID id = rb_to_id(newname);

    rb_alias(mod, id, rb_to_id(oldname));
    rb_clear_cache_by_id(id);
    return mod;
}

#if defined(C_ALLOCA) && defined(THREAD)
# define TMP_PROTECT NODE *__protect_tmp=0
# define TMP_ALLOC(type,n)						   \
    (__protect_tmp = node_newnode(NODE_ALLOCA,				   \
			     str_new(0,sizeof(type)*(n)),0,__protect_tmp), \
     (void*)RSTRING(__protect_tmp->nd_head)->ptr)
#else
# define TMP_PROTECT typedef int foobazzz
# define TMP_ALLOC(type,n) ALLOCA_N(type,n)
#endif

#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) {\
	    char *file = sourcefile;\
	    int line = sourceline;\
            int i;\
	    n = anode;\
	    argv = TMP_ALLOC(VALUE,argc);\
	    for (i=0;i<argc;i++) {\
		argv[i] = rb_eval(self,n->nd_head);\
		n=n->nd_next;\
	    }\
	    sourcefile = file;\
	    sourceline = line;\
        }\
        else {\
	    argc = 0;\
	    argv = 0;\
        }\
    }\
    else {\
        VALUE args = rb_eval(self,n);\
	char *file = sourcefile;\
	int line = sourceline;\
	if (TYPE(args) != T_ARRAY)\
	    args = rb_Array(args);\
        argc = RARRAY(args)->len;\
	argv = ALLOCA_N(VALUE, argc);\
	MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc);\
	sourcefile = file;\
	sourceline = line;\
    }\
}

#define BEGIN_CALLARGS {\
    struct BLOCK *tmp_block = the_block;\
    if (the_iter->iter == ITER_PRE) {\
	the_block = the_block->prev;\
    }\
    PUSH_ITER(ITER_NOT);

#define END_CALLARGS \
    the_block = tmp_block;\
    POP_ITER();\
}

#define MATCH_DATA the_scope->local_vars[node->nd_cnt]

static char* is_defined _((VALUE, NODE*, char*));

static char*
arg_defined(self, node, buf, type)
    VALUE self;
    NODE *node;
    char *buf;
    char *type;
{
    int argc;
    int i;

    if (!node) return type;	/* no args */
    if (nd_type(node) == NODE_ARRAY) {
	argc=node->nd_alen;
        if (argc > 0) {
	    for (i=0;i<argc;i++) {
		if (!is_defined(self, node->nd_head, buf))
		    return 0;
		node = node->nd_next;
	    }
        }
    }
    else if (!is_defined(self, node, buf)) {
	return 0;
    }
    return type;
}
    
static char*
is_defined(self, node, buf)
    VALUE self;
    NODE *node;			/* OK */
    char *buf;
{
    VALUE val;			/* OK */
    int state;

    switch (nd_type(node)) {
      case NODE_SUPER:
      case NODE_ZSUPER:
	if (the_frame->last_func == 0) return 0;
	else if (rb_method_boundp(RCLASS(the_frame->last_class)->super,
				  the_frame->last_func, 1)) {
	    if (nd_type(node) == NODE_SUPER) {
		return arg_defined(self, node->nd_args, buf, "super");
	    }
	    return "super";
	}
	break;

      case NODE_VCALL:
      case NODE_FCALL:
	val = CLASS_OF(self);
	goto check_bound;

      case NODE_CALL:
	if (!is_defined(self, node->nd_recv, buf)) return 0;
	PUSH_TAG(PROT_NONE);
	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 (rb_method_boundp(val, node->nd_mid, nd_type(node)== NODE_CALL)) {
	    return arg_defined(self, node->nd_args, buf, "method");
	}
	break;

      case NODE_MATCH2:
      case NODE_MATCH3:
	return "method";

      case NODE_YIELD:
	if (iterator_p()) {
	    return "yield";
	}
	break;

      case NODE_SELF:
	return "self";

      case NODE_NIL:
	return "nil";

      case NODE_TRUE:
	return "true";

      case NODE_FALSE:
	return "false";

      case NODE_ATTRSET:
      case NODE_OP_ASGN1:
      case NODE_OP_ASGN2:
      case NODE_MASGN:
      case NODE_LASGN:
      case NODE_DASGN:
      case NODE_DASGN_PUSH:
      case NODE_GASGN:
      case NODE_IASGN:
      case NODE_CASGN:
	return "assignment";

      case NODE_LVAR:
	return "local-variable";
      case NODE_DVAR:
	return "local-variable(in-block)";

      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((NODE*)the_frame->cbase, node->nd_vid)) {
	    return "constant";
	}
	break;

      case NODE_COLON2:
	PUSH_TAG(PROT_NONE);
	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(PROT_NONE);
	if ((state = EXEC_TAG()) == 0) {
	    rb_eval(self, node);
	}
	POP_TAG();
	if (!state) {
	    return "expression";
	}
	break;
    }
    return 0;
}

static int handle_rescue _((VALUE,NODE*));

static void blk_free();

static VALUE
obj_is_block(block)
    VALUE block;
{
    if (TYPE(block) == T_DATA && RDATA(block)->dfree == blk_free) {
	return TRUE;
    }
    return FALSE;
}

static VALUE
obj_is_proc(proc)
    VALUE proc;
{
    if (obj_is_block(proc) && obj_is_kind_of(proc, cProc)) {
	return TRUE;
    }
    return FALSE;
}

static VALUE
set_trace_func(obj, trace)
    VALUE obj, trace;
{
    if (NIL_P(trace)) {
	trace_func = 0;
	return Qnil;
    }
    if (!obj_is_proc(trace)) {
	TypeError("trace_func needs to be Proc");
    }
    return trace_func = trace;
}

static void
call_trace_func(event, file, line, self, id)
    char *event;
    char *file;
    int line;
    VALUE self;
    ID id;
{
    int state;
    volatile VALUE trace;
    struct FRAME *prev;
    char *file_save = sourcefile;
    int line_save = sourceline;

    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(PROT_NONE);
    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;
    sourceline = line_save;
    sourcefile = file_save;
    if (state) JUMP_TAG(state);
}

static void return_check _((void));
#define return_value(v) prot_tag->retval = (v)

static VALUE
rb_eval(self, node)
    VALUE self;
    NODE * volatile node;
{
    int state;
    volatile VALUE result = Qnil;

#define RETURN(v) { result = (v); goto finish; }

  again:
    if (!node) RETURN(Qnil);

    switch (nd_type(node)) {
      case NODE_BLOCK:	
	if (!node->nd_next) {
	    node = node->nd_head;
	    goto again;
	}
	while (node) {
	    result = rb_eval(self, node->nd_head);
	    node = node->nd_next;
	}
	break;

      case NODE_POSTEXE:
	f_END();
	nd_set_type(node, NODE_NIL); /* exec just once */
	result = Qnil;
	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(literal match) */
      case NODE_MATCH2:
	result = reg_match(rb_eval(self,node->nd_recv),
			   rb_eval(self,node->nd_value));
	break;

	/* nodes for speed-up(literal match) */
      case NODE_MATCH3:
        {
	    VALUE r = rb_eval(self,node->nd_recv);
	    VALUE l = rb_eval(self,node->nd_value);
	    if (TYPE(r) == T_STRING) {
		result = reg_match(l, r);
	    }
	    else {
		result = rb_funcall(r, match, 1, l);
	    }
	}
	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_TRUE:
	RETURN(TRUE);

      case NODE_FALSE:
	RETURN(FALSE);

      case NODE_IF:
	sourceline = nd_line(node);
	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->nd_file, nd_line(tag),
					self, the_frame->last_func);	
		    }
		    sourceline = nd_line(tag);
		    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(PROT_NONE);
	switch (state = EXEC_TAG()) {
	  case 0:
	    sourceline = nd_line(node);
	    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)));
	    break;

	  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(PROT_NONE);
	switch (state = EXEC_TAG()) {
	  case 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)));
	    break;

	  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_BLOCK_PASS:
	result = block_pass(self, node);
	break;

      case NODE_ITER:
      case NODE_FOR:
	{
	  iter_retry:
	    PUSH_BLOCK(node->nd_var, node->nd_body);
	    PUSH_TAG(PROT_FUNC);

	    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;
		    char *file = sourcefile;
		    int line = sourceline;

		    recv = rb_eval(self, node->nd_iter);
		    PUSH_ITER(ITER_PRE);
		    sourcefile = file;
		    sourceline = line;
		    result = rb_call(CLASS_OF(recv),recv,each,0,0,0);
		    POP_ITER();
		}
	    }
	    else if (_block.tag->dst == state) {
		state &= TAG_MASK;
		if (state == TAG_RETURN) {
		    result = prot_tag->retval;
		}
	    }
	    POP_TAG();
	    POP_BLOCK();
	    switch (state) {
	      case 0:
		break;

	      case TAG_RETRY:
		goto iter_retry;

	      case TAG_BREAK:
		result = Qnil;
		break;
	      case TAG_RETURN:
		return_value(result);
		/* fall through */
	      default:
		JUMP_TAG(state);
	    }
	}
	break;

      case NODE_BREAK:
	JUMP_TAG(TAG_BREAK);
	break;

      case NODE_NEXT:
	JUMP_TAG(TAG_NEXT);
	break;

      case NODE_REDO:
	JUMP_TAG(TAG_REDO);
	break;

      case NODE_RETRY:
	JUMP_TAG(TAG_RETRY);
	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;

	    PUSH_TAG(PROT_NONE);
	    if ((state = EXEC_TAG()) == 0) {
		result = rb_eval(self, node->nd_head);
	    }
	    POP_TAG();
	    if (state == TAG_RAISE) {
		NODE * volatile resq = node->nd_resq;

		while (resq) {
		    if (handle_rescue(self, resq)) {
			state = 0;
			PUSH_TAG(PROT_NONE);
			if ((state = EXEC_TAG()) == 0) {
			    result = rb_eval(self, resq->nd_body);
			}
			POP_TAG();
			if (state == 0) {
			    errinfo = e_info;
			}
			else if (state == 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(PROT_NONE);
	if ((state = EXEC_TAG()) == 0) {
	    result = rb_eval(self, node->nd_head);
	}
	POP_TAG();
	if (node->nd_ensr) {
	    VALUE retval = prot_tag->retval; /* save retval */

	    rb_eval(self, node->nd_ensr);
	    return_value(retval);
	}
	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:
	if (node->nd_stts) {
	    return_value(rb_eval(self, node->nd_stts));
	}
	return_check();
	JUMP_TAG(TAG_RETURN);
	break;

      case NODE_CALL:
	{
	    VALUE recv;
	    int argc; VALUE *argv; /* used in SETUP_ARGS */
	    TMP_PROTECT;

	    BEGIN_CALLARGS;
	    recv = rb_eval(self, node->nd_recv);
	    SETUP_ARGS(node->nd_args);
	    END_CALLARGS;

	    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 */
	    TMP_PROTECT;

	    BEGIN_CALLARGS;
	    SETUP_ARGS(node->nd_args);
	    END_CALLARGS;

	    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 */
	    TMP_PROTECT;

	    if (the_frame->last_class == 0) {	
		NameError("superclass method `%s' disabled",
			  rb_id2name(the_frame->last_func));
	    }
	    if (nd_type(node) == NODE_ZSUPER) {
		argc = the_frame->argc;
		argv = the_frame->argv;
	    }
	    else {
		BEGIN_CALLARGS;
		SETUP_ARGS(node->nd_args);
		END_CALLARGS;
	    }

	    PUSH_ITER(the_iter->iter?ITER_PRE:ITER_NOT);
	    result = rb_call(RCLASS(the_frame->last_class)->super,
			     the_frame->self, the_frame->last_func,
			     argc, argv, 3);
	    POP_ITER();
	}
	break;

      case NODE_SCOPE:
	{
	    VALUE save = the_frame->cbase;

	    PUSH_SCOPE();
	    PUSH_TAG(PROT_NONE);
	    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;
	    }
	    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;
	    TMP_PROTECT;

	    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);
	    if (node->nd_mid == 0) {      /* OR */
		if (RTEST(val)) break;
		val = rb_eval(self, rval);
	    }
	    else if (node->nd_mid == 1) { /* AND */
		if (!RTEST(val)) break;
		val = rb_eval(self, rval);
	    }
	    else {
		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);
	    if (node->nd_next->nd_mid == 0) {      /* OR */
		if (RTEST(val)) break;
		val = rb_eval(self, node->nd_value);
	    }
	    else if (node->nd_next->nd_mid == 1) { /* AND */
		if (!RTEST(val)) break;
		val = rb_eval(self, node->nd_value);
	    }
	    else {
		val = rb_funcall(val, node->nd_next->nd_mid, 1,
				 rb_eval(self, node->nd_value));
	    }

	    rb_funcall2(recv, node->nd_next->nd_aid, 1, &val);
	    result = val;
	}
	break;

      case NODE_OP_ASGN_AND:
	result = rb_eval(self, node->nd_head);
	if (RTEST(result)) {
	    result = rb_eval(self, node->nd_value);
	}
	break;

      case NODE_OP_ASGN_OR:
	result = rb_eval(self, node->nd_head);
	if (!RTEST(result)) {
	    result = rb_eval(self, node->nd_value);
	}
	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");
	result = rb_eval(self, node->nd_value);
	the_scope->local_vars[node->nd_cnt] = result;
	break;

      case NODE_DASGN:
	result = dyna_var_asgn(node->nd_vid, rb_eval(self, node->nd_value));
	break;

      case NODE_DASGN_PUSH:
	result = rb_eval(self, node->nd_value);
	dvar_asgn_push(node->nd_vid, result);
	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 (RTEST(verbose) &&
		ev_const_defined((NODE*)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((NODE*)the_frame->cbase, node->nd_vid);
	break;

      case NODE_BLOCK_ARG:
	if (the_scope->local_vars == 0)
	    Bug("unexpected block argument");
	if (iterator_p()) {
	    result = f_lambda();
	    the_scope->local_vars[node->nd_cnt] = result;
	}
	else {
	    result = Qnil;
	}
	break;

      case NODE_COLON2:
	{
	    VALUE klass;

	    klass = rb_eval(self, node->nd_head);
	    switch (TYPE(klass)) {
	      case T_CLASS:
	      case T_MODULE:
		break;
	      default:
		Check_Type(klass, T_CLASS);
		break;
	    }
	    result = rb_const_get_at(klass, node->nd_mid);
	}
	break;

      case NODE_COLON3:
	result = rb_const_get_at(cObject, 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,0);
			eval_tree = 0;
			rb_in_eval--;
			if (nerrs > 0) {
			    compile_error("string expansion");
			}
		    }
		    str2 = rb_eval(self, list->nd_head);
		    str2 = obj_as_string(str2);
		}
		if (str2) {
		    str_cat(str, RSTRING(str2)->ptr, RSTRING(str2)->len);
		    if (str_tainted(str2)) str_taint(str);
		}
		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;

	    if (the_class == cObject && node->nd_mid == init) {
		Warn("re-defining Object#initialize may cause infinite loop");
	    }
	    body = search_method(the_class, node->nd_mid, &origin);
	    if (body) {
		if (origin == the_class) {
		    Warning("discarding old %s", rb_id2name(node->nd_mid));
		}
		rb_clear_cache_by_id(node->nd_mid);
	    }

	    if (SCOPE_TEST(SCOPE_PRIVATE) || node->nd_mid == init) {
		noex = NOEX_PRIVATE;
	    }
	    else if (SCOPE_TEST(SCOPE_PROTECTED)) {
		noex = NOEX_PROTECTED;
	    }
	    else {
		noex = NOEX_PUBLIC;
	    }
	    if (body && origin == the_class && body->nd_noex & NOEX_UNDEF) {
		noex |= NOEX_UNDEF;
	    }
	    rb_add_method(the_class, node->nd_mid, node->nd_defn, noex);
	    if (scope_vmode == SCOPE_MODFUNC) {
		rb_add_method(rb_singleton_class(the_class),
			      node->nd_mid, node->nd_defn, NOEX_PUBLIC);
		rb_funcall(the_class, rb_intern("singleton_method_added"),
			   1, INT2FIX(node->nd_mid));
	    }
	    if (FL_TEST(the_class, FL_SINGLETON)) {
		rb_funcall(rb_iv_get(the_class, "__attached__"),
			   rb_intern("singleton_method_added"),
			   1, INT2FIX(node->nd_mid));
	    }
	    else {
		rb_funcall(the_class, rb_intern("method_added"),
			   1, INT2FIX(node->nd_mid));
	    }
	    result = Qnil;
	}
	break;

      case NODE_DEFS:
	if (node->nd_defn) {
	    VALUE recv = rb_eval(self, node->nd_recv);
	    VALUE klass;
	    NODE *body = 0;

	    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));
	    }

	    klass = rb_singleton_class(recv);
	    if (st_lookup(RCLASS(klass)->m_tbl, node->nd_mid, &body)) {
		Warning("redefine %s", rb_id2name(node->nd_mid));
	    }
	    rb_clear_cache_by_id(node->nd_mid);
	    rb_add_method(klass, node->nd_mid, node->nd_defn, 
			  NOEX_PUBLIC|(body?body->nd_noex&NOEX_UNDEF:0));
	    rb_funcall(recv, rb_intern("singleton_method_added"),
		       1, INT2FIX(node->nd_mid));
	    result = Qnil;
	}
	break;

      case NODE_UNDEF:
	{
	    VALUE origin;
	    NODE *body;

	    body = search_method(the_class, node->nd_mid, &origin);
	    if (!body || !body->nd_body) {
		char *s0 = " class";
		VALUE klass = the_class;

		if (FL_TEST(the_class, FL_SINGLETON)) {
		    VALUE obj = rb_iv_get(the_class, "__attached__");
		    switch (TYPE(obj)) {
		      case T_MODULE:
		      case T_CLASS:
			klass = obj;
			s0 = "";
		    }
		}
		NameError("undefined method `%s' for%s `%s'",
			  rb_id2name(node->nd_mid),s0,rb_class2name(klass));
	    }
	    rb_clear_cache_by_id(node->nd_mid);
	    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);
	rb_funcall(the_class, rb_intern("method_added"),
		   1, INT2FIX(node->nd_mid));
	result = Qnil;
	break;

      case NODE_VALIAS:
	rb_alias_variable(node->nd_new, node->nd_old);
	result = Qnil;
	break;

      case NODE_CLASS:
	{
	    VALUE super, klass, tmp;

	    if (node->nd_super) {
		super = superclass(self, node->nd_super);
	    }
	    else {
		super = 0;
	    }

	    if (rb_const_defined_at(the_class, node->nd_cname) &&
		(the_class != cObject ||
		 !rb_autoload_defined(node->nd_cname))) {

		klass = rb_const_get_at(the_class, node->nd_cname);
		if (TYPE(klass) != T_CLASS) {
		    TypeError("%s is not a class", rb_id2name(node->nd_cname));
		}
		if (super) {
		    tmp = RCLASS(klass)->super;
		    if (FL_TEST(tmp, FL_SINGLETON)) {
			tmp = RCLASS(tmp)->super;
		    }
		    while (TYPE(tmp) == T_ICLASS) {
			tmp = RCLASS(tmp)->super;
		    }
		    if (tmp != super) {
			TypeError("superclass mismatch for %s",
				  rb_id2name(node->nd_cname));
		    }
		}
		if (safe_level >= 3) {
		    Raise(eSecurityError, "extending class prohibited");
		}
		rb_clear_cache();
	    }
	    else {
		if (!super) super = cObject;
		klass = rb_define_class_id(node->nd_cname, super);
		rb_const_set(the_class, node->nd_cname, klass);
		rb_set_class_path(klass,the_class,rb_id2name(node->nd_cname));
		obj_call_init(klass);
	    }

	    return module_setup(klass, node->nd_body);
	}
	break;

      case NODE_MODULE:
	{
	    VALUE module;

	    if (rb_const_defined_at(the_class, node->nd_cname) &&
		(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 >= 3) {
		    Raise(eSecurityError, "extending module prohibited");
		}
	    }
	    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));
		obj_call_init(module);
	    }

	    result = module_setup(module, node->nd_body);
	}
	break;

      case NODE_SCLASS:
	{
	    VALUE klass;

	    klass = rb_eval(self, node->nd_recv);
	    if (FIXNUM_P(klass)) {
		TypeError("No virtual class for Fixnums");
	    }
	    if (NIL_P(klass)) {
		TypeError("No virtual class for nil");
	    }
	    if (rb_special_const_p(klass)) {
		TypeError("No virtual class for special constants");
	    }
	    if (FL_TEST(CLASS_OF(klass), FL_SINGLETON)) {
		rb_clear_cache();
	    }
	    klass = rb_singleton_class(klass);
	    
	    result = module_setup(klass, node->nd_body);
	}
	break;

      case NODE_DEFINED:
	{
	    char buf[20];
	    char *desc = is_defined(self, node->nd_head, buf);

	    if (desc) result = str_new2(desc);
	    else result = FALSE;
	}
	break;

    case NODE_NEWLINE:
	sourcefile = node->nd_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;
{
    int state;
    VALUE save = the_frame->cbase;
    VALUE result;		/* OK */
    char *file = sourcefile;
    int line = sourceline;
    TMP_PROTECT;

    /* fill c-ref */
    node->nd_clss = module;
    node = node->nd_body;

    PUSH_CLASS();
    the_class = module;
    PUSH_SCOPE();

    if (node->nd_rval) the_frame->cbase = node->nd_rval;
    if (node->nd_tbl) {
	VALUE *vars = TMP_ALLOC(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(PROT_NONE);
    if ((state = EXEC_TAG()) == 0) {
	if (trace_func) {
	    call_trace_func("class", file, line,
			    the_class, the_frame->last_func);
	}
	result = rb_eval(the_class, node->nd_body);
    }
    POP_TAG();
    POP_SCOPE();
    POP_CLASS();

    the_frame->cbase = save;
    if (trace_func) {
	call_trace_func("end", file, line, 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_iter_break()
{
    JUMP_TAG(TAG_BREAK);
}

static void rb_longjmp _((int, VALUE)) NORETURN;
static VALUE make_backtrace _((void));

static void
rb_longjmp(tag, mesg)
    int tag;
    VALUE mesg;
{
    VALUE at;

    if (NIL_P(mesg)) mesg = errinfo;
    if (NIL_P(mesg)) {
	mesg = exc_new(eRuntimeError, 0, 0);
    }

    at = get_backtrace(mesg);
    if (NIL_P(at) && sourcefile && !NIL_P(mesg)) {
	at = make_backtrace();
	set_backtrace(mesg, at);
    }
    if (!NIL_P(mesg)) {
	errinfo = mesg;
    }

    if (debug && !NIL_P(errinfo) && !obj_is_kind_of(errinfo, eSystemExit)) {
	fprintf(stderr, "Exception `%s' at %s:%d\n",
		rb_class2name(CLASS_OF(errinfo)),
		sourcefile, sourceline);
    }

    trap_restore_mask();
    if (trace_func && tag != TAG_FATAL) {
	call_trace_func("raise", sourcefile, sourceline,
			the_frame->self, the_frame->last_func);
    }
    JUMP_TAG(tag);
}

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, arg3;
    VALUE etype, mesg;
    int n;

    etype = eRuntimeError;
    mesg = Qnil;
    switch (n = rb_scan_args(argc, argv, "03", &arg1, &arg2, &arg3)) {
      case 1:
	mesg = arg1;
	break;
      case 3:
      case 2:
	etype = arg1;
	mesg = arg2;
	break;
    }

    if (!NIL_P(mesg)) {
	if (n >= 2) {
	    mesg = rb_funcall(etype, rb_intern("new"), 1, mesg);
	}
	else if (TYPE(mesg) == T_STRING) {
	    mesg = exc_new3(eRuntimeError, mesg);
	}
	if (!obj_is_kind_of(mesg, eException)) {
	    TypeError("casting non-exception");
	}
	set_backtrace(mesg, arg3);
    }

    PUSH_FRAME();		/* fake frame */
    *the_frame = *_frame.prev->prev;
    rb_longjmp(TAG_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;
    volatile VALUE result = Qnil;
    struct BLOCK *block;
    struct SCOPE *old_scope;
    struct FRAME frame;
    int state;
    static unsigned serial = 1;

    if (!iterator_p() || !the_block) {
	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;
    mark_dvar(block->d_vars);
    the_class = block->klass;
    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(PROT_NONE);
    if ((state = EXEC_TAG()) == 0) {
      redo:
	if (!node) {
	    result = Qnil;
	}
	else if (nd_type(node) == NODE_CFUNC) {
	    result = (*node->nd_cfnc)(val, node->nd_tval, self);
	}
	else {
	    result = rb_eval(self, node);
	}
    }
    else {
	switch (state) {
	  case TAG_REDO:
	    state = 0;
	    goto redo;
	  case TAG_NEXT:
	    state = 0;
	    result = Qnil;
	    break;
	  case TAG_BREAK:
	  case TAG_RETURN:
	    state |= (serial++ << 8);
	    state |= 0x10;
	    block->tag->dst = state; 
	    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_Array(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_DASGN_PUSH:
	dvar_asgn_push(lhs->nd_vid, val);
	break;

      case NODE_CASGN:
	rb_const_set(the_class, lhs->nd_vid, val);
	break;

      case NODE_MASGN:
	massign(self, lhs, val);
	break;

      case NODE_CALL:
	{
	    VALUE recv;
	    recv = rb_eval(self, lhs->nd_recv);
	    if (!lhs->nd_args->nd_head) {
		/* attr set */
		rb_call(CLASS_OF(recv), recv, lhs->nd_mid, 1, &val, 0);
	    }
	    else {
		/* array set */
		VALUE args;

		args = rb_eval(self, lhs->nd_args);
		RARRAY(args)->ptr[RARRAY(args)->len-1] = val;
		rb_call(CLASS_OF(recv), recv, lhs->nd_mid,
			RARRAY(args)->len, RARRAY(args)->ptr, 0);
	    }
	}
	break;

      default:
	Bug("bug in variable assignment");
	break;
    }
}

VALUE
rb_iterate(it_proc, data1, bl_proc, data2)
    VALUE (*it_proc)(), (*bl_proc)();
    VALUE data1, data2;
{
    int state;
    volatile VALUE retval = Qnil;
    NODE *node = NEW_CFUNC(bl_proc, data2);
    VALUE self = TopSelf;

  iter_retry:
    PUSH_ITER(ITER_PRE);
    PUSH_BLOCK(0, node);
    PUSH_TAG(PROT_NONE);

    state = EXEC_TAG();
    if (state == 0) {
	retval = (*it_proc)(data1);
    }
    if (the_block->tag->dst == state) {
	state &= TAG_MASK;
	if (state == TAG_RETURN) {
	    retval = prot_tag->retval;
	}
    }
    POP_TAG();
    POP_BLOCK();
    POP_ITER();

    switch (state) {
      case 0:
	break;

      case TAG_RETRY:
	goto iter_retry;

      case TAG_BREAK:
	retval = Qnil;
	break;

      case TAG_RETURN:
	return_value(retval);
	/* 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 */
    TMP_PROTECT;

    if (!node->nd_args) {
	return obj_is_kind_of(errinfo, eStandardError);
    }

    BEGIN_CALLARGS;
    SETUP_ARGS(node->nd_args);
    END_CALLARGS;

    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)();
    VALUE data1, data2;
{
    int state;
    volatile VALUE result;
    volatile VALUE e_info = errinfo;

    PUSH_TAG(PROT_NONE);
    if ((state = EXEC_TAG()) == 0) {
      retry_entry:
	result = (*b_proc)(data1);
    }
    else if (state == TAG_RAISE && obj_is_kind_of(errinfo, eStandardError)) {
	if (r_proc) {
	    PUSH_TAG(PROT_NONE);
	    if ((state = EXEC_TAG()) == 0) {
		result = (*r_proc)(data2, errinfo);
	    }
	    POP_TAG();
	    if (state == TAG_RETRY) {
		state = 0;
		goto retry_entry;
	    }
	}
	else {
	    result = Qnil;
	    state = 0;
	}
	if (state == 0) {
	    errinfo = e_info;
	}
    }
    POP_TAG();
    if (state) JUMP_TAG(state);

    return result;
}

VALUE
rb_ensure(b_proc, data1, e_proc, data2)
    VALUE (*b_proc)();
    VALUE (*e_proc)();
    VALUE data1, data2;
{
    int state;
    volatile VALUE result = Qnil;
    VALUE retval;

    PUSH_TAG(PROT_NONE);
    if ((state = EXEC_TAG()) == 0) {
	result = (*b_proc)(data1);
    }
    POP_TAG();
    retval = prot_tag->retval;	/* save retval */
    (*e_proc)(data2);
    return_value(retval);

    if (state) {
	JUMP_TAG(state);
    }
    return result;
}

static int last_call_status;

#define CSTAT_PRIV  1
#define CSTAT_PROT  2
#define CSTAT_VCALL 4

static VALUE
f_missing(argc, argv, obj)
    int argc;
    VALUE *argv;
    VALUE obj;
{
    ID    id;
    VALUE desc = 0;
    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 = any_to_s(obj);
	break;
      default:
	desc = rb_inspect(obj);
	break;
    }
    if (desc) {
	if (last_call_status & CSTAT_PRIV) {
	    format = "private method `%s' called for %s";
	}
	if (last_call_status & CSTAT_PROT) {
	    format = "protected method `%s' called for %s";
	}
	else if (iterator_p()) {
	    format = "undefined iterator `%s' for %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";
	    }
	}
	if (!format) {
	    format = "undefined method `%s' for %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:"");
    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_call0(klass, recv, id, argc, argv, body, nosuper)
    VALUE klass, recv;
    ID    id;
    int argc;			/* OK */
    VALUE *argv;		/* OK */
    NODE *body;
    int nosuper;
{
    NODE *b2;		/* OK */
    volatile VALUE result = Qnil;
    int itr;
    static int tick;
    TMP_PROTECT;

    switch (the_iter->iter) {
      case ITER_PRE:
	itr = ITER_CUR;
	break;
      case ITER_CUR:
      default:
	itr = ITER_NOT;
	break;
    }

    if ((++tick & 0x3ff) == 0 && stack_length() > STACK_LEVEL_MAX)
	Raise(eSysStackError, "stack level too deep");

    PUSH_ITER(itr);
    PUSH_FRAME();
    the_frame->last_func = id;
    the_frame->last_class = nosuper?0:klass;
    the_frame->self = recv;
    the_frame->argc = argc;
    the_frame->argv = argv;

    switch (nd_type(body)) {
      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(klass), rb_id2name(id));
		}
		else {
		    ArgError("too many arguments(%d)", len);
		}
		break;
	    }
	}
	break;

	/* for re-scoped/renamed method */
      case NODE_ZSUPER:
	/* for attr get/set */
      case NODE_ATTRSET:
      case NODE_IVAR:
	result = rb_eval(recv, body);
	break;

      default:
	{
	    int state;
	    VALUE *local_vars;	/* OK */

	    PUSH_SCOPE();

	    if (body->nd_rval) the_frame->cbase = body->nd_rval;
	    if (body->nd_tbl) {
		local_vars = TMP_ALLOC(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(PROT_FUNC);
	    PUSH_VARS();

	    if ((state = EXEC_TAG()) == 0) {
		NODE *node = 0;
		int i;

		if (nd_type(body) == NODE_ARGS) {
		    node = body;
		    body = 0;
		}
		else if (nd_type(body) == NODE_BLOCK) {
		    node = body->nd_head;
		    body = body->nd_next;
		}
		if (node) {
		    if (nd_type(node) != NODE_ARGS) {
			Bug("no argument-node");
		    }

		    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) {
			    /* +2 for $_ and $~ */
			    MEMCPY(local_vars+2, 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);
			}
		    }
		}

		if (trace_func) {
		    call_trace_func("call", b2->nd_file, nd_line(b2),
				    recv, the_frame->last_func);
		}
		result = rb_eval(recv, body);
	    }
	    else if (state == TAG_RETURN) {
		result = prot_tag->retval;
		state = 0;
	    }
	    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);
	    }
	    switch (state) {
	      case 0:
		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:
		if (!iterator_p()) {
		    Raise(eLocalJumpError, "retry outside of rescue clause");
		}
	      default:
		JUMP_TAG(state);
	    }
	}
    }
    POP_FRAME();
    POP_ITER();
    return result;
}

static VALUE
rb_call(klass, recv, mid, argc, argv, scope)
    VALUE klass, recv;
    ID    mid;
    int argc;			/* OK */
    VALUE *argv;		/* OK */
    int scope;
{
    NODE  *body;		/* OK */
    int    noex;
    ID     id = mid;
    struct cache_entry *ent;

    /* is it in the method cache? */
    ent = cache + EXPR1(klass, mid);
    if (ent->mid == mid && ent->klass == klass) {
	klass = ent->origin;
	id    = ent->mid0;
	noex  = ent->noex;
	body  = ent->method;
    }
    else if ((body = rb_get_method_body(&klass, &id, &noex)) == 0) {
	if (scope == 3) {
	    NameError("super: no superclass method `%s'", rb_id2name(mid));
	}
	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_PRIV);

    /* self must be kind of a specified form for private method */
    if ((noex & NOEX_PROTECTED) && !obj_is_kind_of(the_frame->self, klass))
	return rb_undefined(recv, mid, argc, argv, CSTAT_PROT);

    return rb_call0(klass, recv, id, argc, argv, body, noex & NOEX_UNDEF);
}

VALUE
rb_apply(recv, mid, args)
    VALUE recv;
    ID mid;
    VALUE args;
{
    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;

    if (argc == 0) ArgError("no method name given");

    vid = *argv++; argc--;
    PUSH_ITER(iterator_p()?ITER_PRE:ITER_NOT);
    vid = rb_call(CLASS_OF(recv), recv, rb_to_id(vid), argc, argv, 1);
    POP_ITER();

    return vid;
}


#ifdef HAVE_STDARG_PROTOTYPES
#include <stdarg.h>
#define va_init_list(a,b) va_start(a,b)
#else
#include <varargs.h>
#define va_init_list(a,b) va_start(a)
#endif

VALUE
#ifdef HAVE_STDARG_PROTOTYPES
rb_funcall(VALUE recv, ID mid, int n, ...)
#else
rb_funcall(recv, mid, n, va_alist)
    VALUE recv;
    ID mid;
    int n;
    va_dcl
#endif
{
    va_list ar;
    VALUE *argv;

    if (n > 0) {
	int i;

	argv = ALLOCA_N(VALUE, n);

	va_init_list(ar, n);
	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) {
	    snprintf(buf, BUFSIZ, "%s:%d:in `%s'", sourcefile, sourceline,
		    rb_id2name(frame->last_func));
	}
	else {
	    snprintf(buf, BUFSIZ, "%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) {
	    snprintf(buf, BUFSIZ, "%s:%d:in `%s'",
		    frame->file, frame->line,
		    rb_id2name(frame->prev->last_func));
	}
	else {
	    snprintf(buf, BUFSIZ, "%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, place)
    VALUE src;
    char *place;
{
    NODE *node;

    Check_Type(src, T_STRING);
    if (place == 0) place = sourcefile;
    node = compile_string(place, RSTRING(src)->ptr, RSTRING(src)->len);

    if (nerrs == 0) return node;
    return 0;
}

static VALUE
eval(self, src, scope, file, line)
    VALUE self, src, scope;
    char *file;
    int line;
{
    struct BLOCK *data;
    volatile VALUE result = Qnil;
    struct SCOPE * volatile old_scope;
    struct BLOCK * volatile old_block;
    struct RVarmap * volatile old_d_vars;
    int volatile old_vmode;
    struct FRAME frame;
    char *filesave = sourcefile;
    int linesave = sourceline;
    volatile int iter = the_frame->iter;
    int state;

    if (file == 0) {
	file = sourcefile;
	line = sourceline;
    }
    if (!NIL_P(scope)) {
	if (!obj_is_block(scope)) {
	    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 = the_scope;
	the_scope = data->scope;
	old_block = the_block;
	the_block = data->prev;
	old_d_vars = the_dyna_vars;
	the_dyna_vars = data->d_vars;
	old_vmode = scope_vmode;
	scope_vmode = data->vmode;

	self = data->self;
	the_frame->iter = data->iter;
    }
    else {
	if (the_frame->prev) {
	    the_frame->iter = the_frame->prev->iter;
	}
    }
    PUSH_CLASS();
    the_class = ((NODE*)the_frame->cbase)->nd_clss;

    rb_in_eval++;
    if (TYPE(the_class) == T_ICLASS) {
	the_class = RBASIC(the_class)->klass;
    }
    PUSH_TAG(PROT_NONE);
    if ((state = EXEC_TAG()) == 0) {
	sourcefile = file;
	sourceline = line;
	compile(src, file);
	if (nerrs > 0) {
	    compile_error(0);
	}
	result = eval_node(self);
    }
    POP_TAG();
    POP_CLASS();
    rb_in_eval--;
    if (!NIL_P(scope)) {
	the_frame = the_frame->prev;
	the_scope = old_scope;
	the_block = old_block;
	data->d_vars = the_dyna_vars;
	the_dyna_vars = old_d_vars;
	data->vmode = scope_vmode; /* write back visibility mode */
	scope_vmode = old_vmode;
    }
    else {
	the_frame->iter = iter;
    }
    sourcefile = filesave;
    sourceline = linesave;
    if (state) {
	if (state == TAG_RAISE) {
	    VALUE err;
	    VALUE errat;

	    errat = get_backtrace(errinfo);
	    if (strcmp(file, "(eval)") == 0) {
		if (sourceline > 1) {
		    err = RARRAY(errat)->ptr[0];
		    str_cat(err, ": ", 2);
		    str_concat(err, errinfo);
		}
		else {
		    err = str_dup(errinfo);
		}
		errat = Qnil;
		rb_raise(exc_new3(CLASS_OF(errinfo), err));
	    }
	    rb_raise(errinfo);
	}
	JUMP_TAG(state);
    }

    return result;
}

static VALUE
f_eval(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    VALUE src, scope, vfile, vline;
    char *file = "(eval)";
    int line = 0;

    rb_scan_args(argc, argv, "13", &src, &scope, &vfile, &vline);
    if (argc >= 3) {
	Check_Type(vfile, T_STRING);
	file = RSTRING(vfile)->ptr;
    }
    if (argc >= 4) {
	line = NUM2INT(vline);
    }

    Check_SafeStr(src);
    return eval(self, src, scope, file, line);
}

static VALUE
exec_under(func, under, args)
    VALUE (*func)();
    VALUE under;
    void *args;
{
    VALUE val;			/* OK */
    int state;
    int mode;
    VALUE cbase = the_frame->cbase;

    PUSH_CLASS();
    the_class = under;
    PUSH_FRAME();
    the_frame->last_func = _frame.prev->last_func;
    the_frame->last_class = _frame.prev->last_class;
    the_frame->argc = _frame.prev->argc;
    the_frame->argv = _frame.prev->argv;
    the_frame->cbase = (VALUE)node_newnode(NODE_CREF,under,0,cbase);
    mode = scope_vmode;
    SCOPE_SET(SCOPE_PUBLIC);
    PUSH_TAG(PROT_NONE);
    if ((state = EXEC_TAG()) == 0) {
	val = (*func)(args);
    }
    POP_TAG();
    SCOPE_SET(mode);
    POP_FRAME();
    POP_CLASS();
    if (state) JUMP_TAG(state);

    return val;
}

static VALUE
eval_under_i(args)
    VALUE *args;
{
    return eval(args[0], args[1], Qnil, (char*)args[2], (int)args[3]);
}

static VALUE
eval_under(under, self, src, file, line)
    VALUE under, self, src;
    char *file;
    int line;
{
    VALUE args[4];

    args[0] = self;
    args[1] = src;
    args[2] = (VALUE)file;
    args[3] = (VALUE)line;
    return exec_under(eval_under_i, under, args);
}

static VALUE
yield_under_i(self)
    VALUE self;
{
    return rb_yield_0(self, self);
}

static VALUE
yield_under(under, self)
    VALUE under, self;
{
    return exec_under(yield_under_i, under, self);
}

static VALUE
obj_instance_eval(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    char *file = 0;
    int   line = 0;

    if (argc == 0) {
	if (!iterator_p()) {
	    ArgError("block not supplied");
	}
    }
    else if (argc < 4) {
	Check_SafeStr(argv[0]);
	if (argc > 1) file = STR2CSTR(argv[1]);
	if (argc > 2) line = NUM2INT(argv[2]);
    }
    else {
	ArgError("Wrong # of arguments: %s(src) or %s{..}",
		 rb_id2name(the_frame->last_func),
		 rb_id2name(the_frame->last_func));
    }

    if (argc == 0) {
	return yield_under(rb_singleton_class(self), self);
    }
    else {
	return eval_under(rb_singleton_class(self), self, argv[0], file, line);
    }
}

static VALUE
mod_module_eval(argc, argv, mod)
    int argc;
    VALUE *argv;
    VALUE mod;
{
    char *file = 0;
    int   line = 0;

    if (argc == 0) {
	if (!iterator_p()) {
	    ArgError("block not supplied");
	}
    }
    else if (argc < 4) {
	Check_SafeStr(argv[0]);
	if (argc > 1) file = STR2CSTR(argv[1]);
	if (argc > 2) line = NUM2INT(argv[2]);
    }
    else {
	ArgError("Wrong # of arguments: %s(src) or %s{..}",
		 rb_id2name(the_frame->last_func),
		 rb_id2name(the_frame->last_func));
    }

    if (argc == 0) {
	return yield_under(mod, mod);
    }
    else {
	return eval_under(mod, mod, argv[0], file, line);
    }
}

VALUE rb_load_path;

static int
is_absolute_path(path)
    char *path;
{
    if (path[0] == '/') return 1;
# if defined(MSDOS) || defined(NT) || defined(__human68k__)
    if (path[0] == '\\') return 1;
    if (strlen(path) > 2 && path[1] == ':') return 1;
# endif
    return 0;
}

static char*
find_file(file)
    char *file;
{
    extern VALUE rb_load_path;
    VALUE vpath;
    char *path;

    if (is_absolute_path(file)) {
	FILE *f = fopen(file, "r");

	if (f == NULL) return 0;
	fclose(f);
	return file;
    }

    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]);
	}
	vpath = ary_join(rb_load_path, str_new2(RUBY_LIB_SEP));
	Check_SafeStr(vpath);
	path = RSTRING(vpath)->ptr;
    }
    else {
	path = 0;
    }

    return dln_find_file(file, path);
}

VALUE
f_load(obj, fname)
    VALUE obj, fname;
{
    int state;
    char *file;
    volatile ID last_func;
    TMP_PROTECT;

    Check_SafeStr(fname);
#ifndef __MACOS__
    if (RSTRING(fname)->ptr[0] == '~') {
	fname = file_s_expand_path(1, &fname);
    }
#endif
    file = find_file(RSTRING(fname)->ptr);
    if (!file) LoadError("No such file to load -- %s", RSTRING(fname)->ptr);

    PUSH_VARS();
    PUSH_TAG(PROT_NONE);
    PUSH_CLASS();
    the_class = cObject;
    PUSH_SCOPE();
    if (top_scope->local_tbl) {
	int len = top_scope->local_tbl[0]+1;
	ID *tbl = ALLOC_N(ID, len);
	VALUE *vars = TMP_ALLOC(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;
    }
    /* default visibility is private at loading toplevel */
    SCOPE_SET(SCOPE_PRIVATE);

    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();
    POP_VARS();
    if (nerrs > 0) {
	rb_raise(errinfo);
    }
    if (state) JUMP_TAG(state);

    return TRUE;
}

static VALUE rb_features;

static int
rb_provided(feature)
    char *feature;
{
    VALUE *p, *pend;
    char *f;
    int len;

    p = RARRAY(rb_features)->ptr;
    pend = p + RARRAY(rb_features)->len;
    while (p < pend) {
	f = STR2CSTR(*p);
	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 _((char*));
static void thread_loading_done _((void));
#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, fname;
{
    char *ext, *file, *feature, *buf; /* OK */
    VALUE load;

    Check_SafeStr(fname);
    if (rb_provided(RSTRING(fname)->ptr))
	return FALSE;

    ext = strrchr(RSTRING(fname)->ptr, '.');
    if (ext) {
	if (strcmp(".rb", ext) == 0) {
	    feature = file = RSTRING(fname)->ptr;
	    file = find_file(file);
	    if (file) goto rb_load;
	}
	else if (strcmp(".o", ext) == 0) {
	    file = feature = RSTRING(fname)->ptr;
	    if (strcmp(".o", DLEXT) != 0) {
		buf = ALLOCA_N(char, strlen(file)+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 = RSTRING(fname)->ptr;
	    file = find_file(feature);
	    if (file) goto dyna_load;
	}
    }
    buf = ALLOCA_N(char, strlen(RSTRING(fname)->ptr) + 5);
    sprintf(buf, "%s.rb", RSTRING(fname)->ptr);
    file = find_file(buf);
    if (file) {
	fname = str_new2(file);
	feature = buf;
	goto rb_load;
    }
    sprintf(buf, "%s%s", RSTRING(fname)->ptr, DLEXT);
    file = find_file(buf);
    if (file) {
	feature = buf;
	goto dyna_load;
    }
    LoadError("No such file to load -- %s", RSTRING(fname)->ptr);

  dyna_load:
#ifdef THREAD
    if (thread_loading(feature)) return FALSE;
    else {
	int state;
	PUSH_TAG(PROT_NONE);
	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 {
	int state;
	PUSH_TAG(PROT_NONE);
	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;
{
    if (argc == 0) {
	SCOPE_SET(SCOPE_PUBLIC);
    }
    else {
	set_method_visibility(module, argc, argv, NOEX_PUBLIC);
    }
    return module;
}

static VALUE
mod_protected(argc, argv, module)
    int argc;
    VALUE *argv;
    VALUE module;
{
    if (argc == 0) {
	SCOPE_SET(SCOPE_PROTECTED);
    }
    else {
	set_method_visibility(module, argc, argv, NOEX_PROTECTED);
    }
    return module;
}

static VALUE
mod_private(argc, argv, module)
    int argc;
    VALUE *argv;
    VALUE module;
{
    if (argc == 0) {
	SCOPE_SET(SCOPE_PRIVATE);
    }
    else {
	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
top_public(argc, argv)
    int argc;
    VALUE *argv;
{
    return mod_public(argc, argv, cObject);
}

static VALUE
top_private(argc, argv)
    int argc;
    VALUE *argv;
{
    return mod_private(argc, argv, cObject);
}

static VALUE
mod_modfunc(argc, argv, module)
    int argc;
    VALUE *argv;
    VALUE module;
{
    int i;
    ID id;
    NODE *body;

    if (argc == 0) {
	SCOPE_SET(SCOPE_MODFUNC);
	return module;
    }

    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_clear_cache_by_id(id);
	rb_add_method(rb_singleton_class(module), id, body->nd_body, NOEX_PUBLIC);
    }
    return module;
}

static VALUE
mod_append_features(module, include)
    VALUE module, include;
{
    switch (TYPE(include)) {
      case T_CLASS:
      case T_MODULE:
	break;
      default:
	Check_Type(include, T_CLASS);
	break;
    }
    rb_include_module(include, module);

    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_funcall(argv[i], rb_intern("append_features"), 1, module);
    }
    return module;
}

void
obj_call_init(obj)
    VALUE obj;
{
    PUSH_ITER(iterator_p()?ITER_PRE:ITER_NOT);
    rb_funcall2(obj, init, the_frame->argc, the_frame->argv);
    POP_ITER();
}

VALUE
class_new_instance(argc, argv, klass)
    int argc;
    VALUE *argv;
    VALUE klass;
{
    VALUE obj;

    if (FL_TEST(klass, FL_SINGLETON)) {
	TypeError("can't create instance of virtual class");
    }
    obj = obj_alloc(klass);
    obj_call_init(obj);

    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();

static void
errinfo_setter(val, id, var)
    VALUE val;
    ID id;
    VALUE *var;
{
    if (!obj_is_kind_of(val, eException)) {
	TypeError("assigning non-exception to $!");
    }
    *var = val;
}

static VALUE
errat_getter(id)
    ID id;
{
    return get_backtrace(errinfo);
}

static void
errat_setter(val, id, var)
    VALUE val;
    ID id;
    VALUE *var;
{
    if (NIL_P(errinfo)) {
	ArgError("$! not set");
    }
    set_backtrace(errinfo, val);
}

VALUE f_global_variables();
VALUE f_instance_variables();

VALUE
f_local_variables()
{
    ID *tbl;
    int n, i;
    VALUE ary = ary_new();
    struct RVarmap *vars;

    tbl = the_scope->local_tbl;
    if (tbl) {
	n = *tbl++;
	for (i=2; i<n; i++) {	/* skip first 2 ($_ and $~) */
	    ary_push(ary, str_new2(rb_id2name(tbl[i])));
	}
    }

    vars = the_dyna_vars;
    while (vars) {
	if (vars->id) {
	    ary_push(ary, str_new2(rb_id2name(vars->id)));
	}
	vars = vars->next;
    }

    return ary;
}

static VALUE f_catch();
static VALUE f_throw();

struct end_proc_data {
    void (*func)();
    VALUE data;
    struct end_proc_data *next;
} *end_proc_data;

void
rb_set_end_proc(func, data)
    void (*func)();
    VALUE data;
{
    struct end_proc_data *link = ALLOC(struct end_proc_data);

    link->next = end_proc_data;
    link->func = func;
    link->data = data;
    rb_global_variable(&link->data);
    end_proc_data = link;
}

static void
call_end_proc(data)
    VALUE data;
{
    proc_call(data, Qnil);
}

static void
f_END()
{
    PUSH_FRAME();
    the_frame->argc = 0;
    rb_set_end_proc(call_end_proc, f_lambda());
    POP_FRAME();
}

static VALUE
f_at_exit()
{
    VALUE proc;

    proc = f_lambda();

    rb_set_end_proc(call_end_proc, proc);
    return proc;
}

static void
exec_end_proc()
{
    struct end_proc_data *link = end_proc_data;

    while (link) {
	(*link->func)(link->data);
	link = link->next;
    }
}

void
Init_eval()
{
    init = rb_intern("initialize");
    eqq = rb_intern("===");
    each = rb_intern("each");

    aref = rb_intern("[]");
    aset = rb_intern("[]=");
    match = rb_intern("=~");

    rb_global_variable((VALUE*)&top_scope);
    rb_global_variable((VALUE*)&eval_tree_begin);

    rb_global_variable((VALUE*)&eval_tree);
    rb_global_variable((VALUE*)&the_dyna_vars);

    rb_define_virtual_variable("$@", errat_getter, errat_setter);
    rb_define_hooked_variable("$!", &errinfo, 0, errinfo_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("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("at_exit", f_at_exit, 0);

    rb_define_global_function("catch", f_catch, 1);
    rb_define_global_function("throw", f_throw, -1);
    rb_define_global_function("global_variables", f_global_variables, 0);
    rb_define_global_function("local_variables", f_local_variables, 0);

    rb_define_method(mKernel, "send", f_send, -1);
    rb_define_method(mKernel, "__send__", f_send, -1);
    rb_define_method(mKernel, "instance_eval", obj_instance_eval, -1);

    rb_define_private_method(cModule, "append_features", mod_append_features, 1);
    rb_define_private_method(cModule, "extend_object", mod_extend_object, 1);
    rb_define_private_method(cModule, "include", mod_include, -1);
    rb_define_private_method(cModule, "public", mod_public, -1);
    rb_define_private_method(cModule, "protected", mod_protected, -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, "public_class_method", mod_public_method, -1);
    rb_define_method(cModule, "private_class_method", mod_private_method, -1);
    rb_define_method(cModule, "module_eval", mod_module_eval, -1);
    rb_define_method(cModule, "class_eval", mod_module_eval, -1);

    rb_define_private_method(cModule, "remove_method", mod_remove_method, 1);
    rb_define_private_method(cModule, "undef_method", mod_undef_method, 1);
    rb_define_private_method(cModule, "alias_method", mod_alias_method, 2);

    rb_define_singleton_method(cModule, "nesting", mod_nesting, 0);
    rb_define_singleton_method(cModule, "constants", mod_s_constants, 0);

    rb_define_singleton_method(TopSelf, "include", top_include, -1);
    rb_define_singleton_method(TopSelf, "public", top_public, -1);
    rb_define_singleton_method(TopSelf, "private", top_private, -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;
{
    while (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);
	gc_mark(data->klass);
	data = data->prev;
    }
}

static void
blk_free(data)
    struct BLOCK *data;
{
    struct BLOCK *tmp;

    while (data) {
	free(data->frame.argv);
	tmp = data;
	data = data->prev;
	free(tmp);
    }
}

static void
blk_copy_prev(block)
    struct BLOCK *block;
{
    struct BLOCK *tmp;

    while (block->prev) {
	tmp = ALLOC_N(struct BLOCK, 1);
	MEMCPY(tmp, block->prev, struct BLOCK, 1);
	tmp->frame.argv = ALLOC_N(VALUE, tmp->frame.argc);
	MEMCPY(tmp->frame.argv, block->frame.argv, VALUE, tmp->frame.argc);
	block->prev = tmp;
	block = tmp;
    }
}


static VALUE
bind_clone(self)
    VALUE self;
{
    struct BLOCK *orig, *data;
    VALUE bind;

    Data_Get_Struct(self, struct BLOCK, orig);
    bind = Data_Make_Struct(self,struct BLOCK,blk_mark,blk_free,data);
    MEMCPY(data, orig, struct BLOCK, 1);
    data->frame.argv = ALLOC_N(VALUE, orig->frame.argc);
    MEMCPY(data->frame.argv, orig->frame.argv, VALUE, orig->frame.argc);

    if (data->iter) {
	blk_copy_prev(data);
    }
    else {
	data->prev = 0;
    }

    return bind;
}

static VALUE
f_binding(self)
    VALUE self;
{
    struct BLOCK *data;
    VALUE bind;

    PUSH_BLOCK(0,0);
    bind = Data_Make_Struct(cBinding,struct BLOCK,blk_mark,blk_free,data);
    MEMCPY(data, the_block, struct BLOCK, 1);

#ifdef THREAD
    data->orig_thread = thread_current();
#endif
    data->iter = f_iterator_p();
    if (the_frame->prev) {
	data->frame.last_func = the_frame->prev->last_func;
    }
    data->frame.argv = ALLOC_N(VALUE, data->frame.argc);
    MEMCPY(data->frame.argv, the_block->frame.argv, VALUE, data->frame.argc);

    if (data->iter) {
	blk_copy_prev(data);
    }
    else {
	data->prev = 0;
    }

    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(klass)
    VALUE klass;
{
    volatile VALUE proc;
    struct BLOCK *data;

    if (!iterator_p() && !f_iterator_p()) {
	ArgError("tryed to create Procedure-Object out of iterator");
    }

    proc = Data_Make_Struct(klass, struct BLOCK, blk_mark, blk_free, data);
    *data = *the_block;

#ifdef THREAD
    data->orig_thread = thread_current();
#endif
    data->iter = data->prev?TRUE:FALSE;
    data->frame.argv = ALLOC_N(VALUE, data->frame.argc);
    MEMCPY(data->frame.argv, the_block->frame.argv, VALUE, data->frame.argc);
    if (data->iter) {
	blk_copy_prev(data);
    }
    else {
	data->prev = 0;
    }

    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;
	}
    }
    obj_call_init(proc);

    return proc;
}

VALUE
f_lambda()
{
    return proc_s_new(cProc);
}

static int
blk_orphan(data)
    struct BLOCK *data;
{
    if (data->scope && data->scope != top_scope &&
	(data->scope->flag & SCOPE_NOSTACK)) {
	return 1;
    }
#ifdef THREAD
    if (data->orig_thread != thread_current()) {
	return 1;
    }
#endif
    return 0;
}

static VALUE
proc_call(proc, args)
    VALUE proc, args;		/* OK */
{
    struct BLOCK * volatile old_block;
    struct BLOCK *data, data_body;
    volatile VALUE result = Qnil;
    int state;
    volatile int orphan;
    volatile int safe = safe_level;

    if (TYPE(args) == T_ARRAY) {
	switch (RARRAY(args)->len) {
	  case 0:
	    args = Qnil;
	    break;
	  case 1:
	    args = RARRAY(args)->ptr[0];
	    break;
	}
    }

    Data_Get_Struct(proc, struct BLOCK, data);
    orphan = blk_orphan(data);

    /* PUSH BLOCK from data */
    old_block = the_block;
#if 0
    the_block = data;
#else
    data_body = *data;
    the_block = &data_body;
#endif
    PUSH_ITER(ITER_CUR);
    the_frame->iter = ITER_CUR;

    if (orphan) {/* orphan procedure */
	if (iterator_p()) {
	    the_block->frame.iter = ITER_CUR;
	}
	else {
	    the_block->frame.iter = ITER_NOT;
	}
    }

    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(PROT_NONE);
    state = EXEC_TAG();
    if (state == 0) {
	result = rb_yield(args);
    }
    POP_TAG();

    POP_ITER();
    if (the_block->tag->dst == state) {
	state &= TAG_MASK;
    }
    the_block = old_block;
    safe_level = safe;

    if (state) {
	if (orphan) {/* orphan procedure */
	    switch (state) {
	      case TAG_BREAK:
		Raise(eLocalJumpError, "break from proc-closure");
		break;
	      case TAG_RETRY:
		Raise(eLocalJumpError, "retry from proc-closure");
		break;
	      case TAG_RETURN:
		Raise(eLocalJumpError, "return from proc-closure");
		break;
	    }
	}
	JUMP_TAG(state);
    }
    return result;
}

static VALUE
block_pass(self, node)
    VALUE self;
    NODE *node;
{
    VALUE block = rb_eval(self, node->nd_body);
    struct BLOCK * volatile old_block;
    struct BLOCK *data, data_body;
    volatile VALUE result = Qnil;
    int state;
    volatile int orphan;
    volatile int safe = safe_level;

    if (NIL_P(block)) {
	return rb_eval(self, node->nd_iter);
    }
    if (obj_is_kind_of(block, cMethod)) {
	block = method_proc(block);
    }
    else if (!obj_is_proc(block)) {
	TypeError("wrong argument type %s (expected Proc)",
		  rb_class2name(CLASS_OF(block)));
    }

    Data_Get_Struct(block, struct BLOCK, data);
    orphan = blk_orphan(data);

    /* PUSH BLOCK from data */
    old_block = the_block;
#if 0
    the_block = data;
#else
    data_body = *data;
    the_block = &data_body;
#endif
    PUSH_ITER(ITER_PRE);
    the_frame->iter = ITER_PRE;
    if (FL_TEST(block, PROC_TAINT)) {
	switch (RBASIC(block)->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(PROT_NONE);
    state = EXEC_TAG();
    if (state == 0) {
	result = rb_eval(self, node->nd_iter);
    }
    POP_TAG();

    POP_ITER();
    if (the_block->tag->dst == state) {
	state &= TAG_MASK;
	orphan = 2;
    }
    the_block = old_block;
    safe_level = safe;

    if (state) {
	if (orphan == 2) {/* escape from orphan procedure */
	    switch (state) {
	      case TAG_BREAK:
		Raise(eLocalJumpError, "break from proc-closure");
		break;
	      case TAG_RETRY:
		Raise(eLocalJumpError, "retry from proc-closure");
		break;
	      case TAG_RETURN:
		Raise(eLocalJumpError, "return from proc-closure");
		break;
	    }
	}
	JUMP_TAG(state);
    }
    return result;
}

struct METHOD {
    VALUE klass, oklass;
    VALUE recv;
    ID id, oid;
    NODE *body;
};

static void
bm_mark(data)
    struct METHOD *data;
{
    gc_mark(data->oklass);
    gc_mark(data->klass);
    gc_mark(data->recv);
    gc_mark(data->body);
}

static VALUE
obj_method(obj, vid)
    VALUE obj;
    VALUE vid;
{
    VALUE method;
    VALUE klass = CLASS_OF(obj);
    ID id;
    NODE *body;
    int noex;
    struct METHOD *data;

    id = rb_to_id(vid);

  again:
    if ((body = rb_get_method_body(&klass, &id, &noex)) == 0) {
	return rb_undefined(obj, rb_to_id(vid), 0, 0, 0);
    }

    if (nd_type(body) == NODE_ZSUPER) {
	klass = RCLASS(klass)->super;
	goto again;
    }

    method = Data_Make_Struct(cMethod, struct METHOD, bm_mark, free, data);
    data->klass = klass;
    data->recv = obj;
    data->id = id;
    data->body = body;
    data->oklass = CLASS_OF(obj);
    data->oid = rb_to_id(vid);

    return method;
}

static VALUE
method_call(argc, argv, method)
    int argc;
    VALUE *argv;
    VALUE method;
{
    VALUE result;
    struct METHOD *data;

    Data_Get_Struct(method, struct METHOD, data);
    PUSH_ITER(iterator_p()?ITER_PRE:ITER_NOT);
    result = rb_call0(data->klass, data->recv, data->id,
		      argc, argv, data->body, 0);
    POP_ITER();
    return result;
}

static VALUE
method_inspect(method)
    VALUE method;
{
    struct METHOD *data;
    VALUE str;
    char *s;

    Data_Get_Struct(method, struct METHOD, data);
    str = str_new2("#<");
    s = rb_class2name(CLASS_OF(method));
    str_cat(str, s, strlen(s));
    str_cat(str, ": ", 2);
    s = rb_class2name(data->oklass);
    str_cat(str, s, strlen(s));
    str_cat(str, "#", 1);
    s = rb_id2name(data->oid);
    str_cat(str, s, strlen(s));
    str_cat(str, ">", 1);

    return str;
}

static VALUE
mproc()
{
    VALUE proc;

    /* emulate ruby's method call */
    PUSH_ITER(ITER_CUR);
    PUSH_FRAME();
    proc = f_lambda();
    POP_FRAME();
    POP_ITER();

    return proc;
}

static VALUE
mcall(args, method)
    VALUE args, method;
{
    if (TYPE(args) == T_ARRAY) {
	return method_call(RARRAY(args)->len, RARRAY(args)->ptr, method);
    }
    return method_call(1, &args, method);
}

static VALUE
method_proc(method)
    VALUE method;
{
    return rb_iterate(mproc, 0, mcall, method);
}

void
Init_Proc()
{
    eLocalJumpError = rb_define_class("LocalJumpError", eStandardError);
    eSysStackError = rb_define_class("SystemStackError", eStandardError);

    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);
    cBinding = rb_define_class("Binding", cObject);
    rb_undef_method(CLASS_OF(cMethod), "new");
    rb_define_method(cBinding, "clone", bind_clone, 0);

    cMethod = rb_define_class("Method", cObject);
    rb_undef_method(CLASS_OF(cMethod), "new");
    rb_define_method(cMethod, "call", method_call, -1);
    rb_define_method(cMethod, "inspect", method_inspect, 0);
    rb_define_method(cMethod, "to_s", method_inspect, 0);
    rb_define_method(cMethod, "to_proc", method_proc, 0);
    rb_define_method(mKernel, "method", obj_method, 1);
}

#ifdef THREAD

static VALUE eThreadError;

int thread_pending = 0;

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 RVarmap *dyna_vars;
    struct BLOCK *block;
    struct iter *iter;
    struct tag *tag;
    VALUE klass;

    VALUE trace;
    int misc;			/* misc. states (vmode/trap_immediate) */

    char *file;
    int   line;

    VALUE 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);
    gc_mark(th->thread);
    if (th->join) gc_mark(th->join->thread);

    gc_mark(th->scope);
    gc_mark(th->dyna_vars);
    gc_mark(th->errinfo);
    gc_mark(th->last_line);
    gc_mark(th->last_match);

    /* mark data in copied stack */
    if (th->status == THREAD_KILLED) return;
    if (th->stk_len == 0) return;  /* stack not active, no need to mark. */
    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
    }
    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;

    if (!curr_thread) return;
    FOREACH_THREAD(th) {
	gc_mark(th->thread);
    } END_FOREACH(th);
}

static void
thread_free(th)
    thread_t th;
{
    if (th->stk_ptr) free(th->stk_ptr);
    th->stk_ptr = 0;
    if (th != main_thread) free(th);
}

static thread_t
thread_check(data)
    VALUE data;
{
    if (TYPE(data) != T_DATA || RDATA(data)->dfree != thread_free) {
	TypeError("wrong argument type %s (expected Thread)",
		  rb_class2name(CLASS_OF(data)));
    }
    return (thread_t)RDATA(data)->data;
}

static void
thread_save_context(th)
    thread_t th;
{
    VALUE v;

    int len = stack_length();
    th->stk_len = 0;
    th->stk_pos = (gc_stack_start<(VALUE*)&v)?gc_stack_start
				             :gc_stack_start - len;
    if (len > th->stk_max)  {
	REALLOC_N(th->stk_ptr, VALUE, len);
	th->stk_max = len;
    }
    th->stk_len = len;
    FLUSH_REGISTER_WINDOWS; 
    MEMCPY(th->stk_ptr, th->stk_pos, VALUE, th->stk_len);

    th->frame = the_frame;
    th->scope = the_scope;
    th->klass = the_class;
    th->dyna_vars = the_dyna_vars;
    th->block = the_block;
    th->misc = scope_vmode | (trap_immediate<<8);
    th->iter = the_iter;
    th->tag = prot_tag;
    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 _((thread_t,int));

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 VALUE th_cmd;
static int   th_sig;

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->klass;
    the_dyna_vars = th->dyna_vars;
    the_block = th->block;
    scope_vmode = th->misc&SCOPE_MASK;
    trap_immediate = th->misc>>8;
    the_iter = th->iter;
    prot_tag = th->tag;
    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_TAG(TAG_FATAL);
	break;

      case 2:
	rb_interrupt();
	break;

      case 3:
	rb_trap_eval(th_cmd, th_sig);
	errno = EINTR;
	break;

      case 4:
	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;
}

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;		/* OK */
    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) {
	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;	/* OK */

	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) {
	curr_thread->file = sourcefile;
	curr_thread->line = sourceline;
	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->status == THREAD_RUNNABLE && 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 _((void));

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()
{
    thread_critical = 0;
    curr_thread->status = THREAD_STOPPED;
    if (curr_thread == curr_thread->next) {
	Raise(eThreadError, "stopping only thread");
    }
    thread_schedule();

    return Qnil;
}

struct timeval time_timeval();

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((32767L<<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->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->klass = 0;
    th->dyna_vars = 0;
    th->block = 0;
    th->iter = 0;
    th->tag = 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, thread_mark, 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) {
	    thread_schedule();
	}
	else thread_pending = 1;
    }
}
#else
int thread_tick = THREAD_TICK;
#endif

static VALUE thread_raise _((int, VALUE*, VALUE));

VALUE
thread_create(fn, arg)
    VALUE (*fn)();
    void *arg;
{
    thread_t th = thread_alloc();
    volatile VALUE thread = th->thread;
    int state;
    enum thread_status status;

#if defined(HAVE_SETITIMER) && !defined(__BOW__)
    static init = 0;

    if (!init) {
	struct itimerval tval;

#ifdef POSIX_SIGNAL
	posix_signal(SIGVTALRM, catch_timer);
	posix_signal(SIGALRM, catch_timer);
#else
	signal(SIGVTALRM, catch_timer);
	signal(SIGALRM, 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(PROT_THREAD);
    if ((state = EXEC_TAG()) == 0) {
	thread_save_context(th);
	if (setjmp(th->context) == 0) {
	    curr_thread = th;
	    th->result = (*fn)(arg, th);
	}
    }
    POP_TAG();
    status = th->status;
    thread_remove();
    if (state &&  status != THREAD_TO_KILL && !NIL_P(errinfo)) {
	if (state == TAG_FATAL) { 
	    /* fatal error within this thread, need to stop whole script */
	    main_thread->errinfo = errinfo;
	    thread_cleanup();
	}
	else if (obj_is_kind_of(errinfo, eSystemExit)) {
	    /* delegate exception to main_thread */
	    thread_raise(1, &errinfo, main_thread->thread);
	}
	else if (thread_abort || curr_thread->abort || RTEST(debug)) {
	    VALUE err = exc_new(eSystemExit, 0, 0);
	    error_print();
	    /* exit on main_thread */
	    thread_raise(1, &err, main_thread->thread);
	}
	else {
	    curr_thread->errinfo = errinfo;
	}
    }
    thread_schedule();
    return 0;			/* not reached */
}

static VALUE
thread_yield(arg, th) 
    int arg;
    thread_t th;
{
    scope_dup(the_block->scope);
    return 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)) {
	VALUE oldbt = get_backtrace(th->errinfo);
	VALUE errat = make_backtrace();

	ary_unshift(errat, ary_entry(oldbt, 0));
	set_backtrace(th->errinfo, 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_stop_p(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);
}

void
thread_trap_eval(cmd, sig)
    VALUE cmd;
    int sig;
{
    thread_critical = 0;
    if (!thread_dead(curr_thread)) {
	thread_ready(curr_thread);
	rb_trap_eval(cmd, sig);
	return;
    }
    thread_ready(main_thread);
    thread_save_context(curr_thread);
    if (setjmp(curr_thread->context)) {
	return;
    }
    th_cmd = cmd;
    th_sig = sig;
    curr_thread = main_thread;
    thread_restore_context(curr_thread, 3);
}

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);
    }

    if (curr_thread->status != THREAD_KILLED)
	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, 4);
    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", eStandardError);
    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, "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_stop_p, 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

static VALUE
f_catch(dmy, tag)
    VALUE dmy, tag;
{
    int state;
    ID t;
    VALUE val;			/* OK */

    t = rb_to_id(tag);
    PUSH_TAG(t);
    if ((state = EXEC_TAG()) == 0) {
	val = rb_yield(tag);
    }
    else if (state == TAG_THROW && t == prot_tag->dst) {
	val = prot_tag->retval;
	state = 0;
    }
    POP_TAG();
    if (state) JUMP_TAG(state);

    return val;
}

static VALUE
catch_i(tag)
    ID tag;
{
    return f_catch(0, FIX2INT(tag));
}

VALUE
rb_catch(tag, proc, data)
    char *tag;
    VALUE (*proc)();
    VALUE data;
{
    return rb_iterate(catch_i, rb_intern(tag), proc, data);
}


static VALUE f_throw _((int,VALUE*)) NORETURN;

static VALUE
f_throw(argc, argv)
    int argc;
    VALUE *argv;
{
    VALUE tag, value;
    ID t;
    struct tag *tt = prot_tag;

    rb_scan_args(argc, argv, "11", &tag, &value);
    t = rb_to_id(tag);

    while (tt) {
	if (tt->tag == t) {
	    tt->dst = t;
	    break;
	}
#ifdef THREAD
	if (tt->tag == PROT_THREAD) {
	    Raise(eThreadError, "uncaught throw `%s' in thread 0x%x",
		  rb_id2name(t),
		  curr_thread);
	}
#endif
	tt = tt->prev;
    }
    if (!tt) {
	NameError("uncaught throw `%s'", rb_id2name(t));
    }
    return_value(value);
    trap_restore_mask();
    JUMP_TAG(TAG_THROW);
    /* not reached */
}

void
rb_throw(tag, val)
    char *tag;
    VALUE val;
{
    VALUE argv[2];
    ID t = rb_intern(tag);

    argv[0] = FIX2INT(t);
    argv[1] = val;
    f_throw(2, argv);
}

static void
return_check()
{
#ifdef THREAD
    struct tag *tt = prot_tag;

    while (tt) {
	if (tt->tag == PROT_FUNC) {
	    break;
	}
	if (tt->tag == PROT_THREAD) {
	    Raise(eThreadError, "return from within thread 0x%x",
		  curr_thread);
	}
	tt = tt->prev;
    }
#endif
}

