celtic / rubyex

a Ruby VM/interpreter

rubyex / vm / context.cpp
100644 153 lines (126 sloc) 4.957 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <iostream>
#include <exception>
#include "context.h"
#include "rmethod.h"
#include "rexception.h"
 
Context::Context(RubyEnvironment &_environment): binding(linked_ptr<Binding>(new Binding(_environment, O2V(_environment.main), _environment.Object))), outer_context(NULL)
{ }
 
Context::Context(RubyEnvironment &_environment, RubyValue _context, RubyModule *_def_target, Context *_outer_context): binding(linked_ptr<Binding>(new Binding(_environment, _context, _def_target))), outer_context(_outer_context)
{ }
 
Context::Context(linked_ptr<Binding> &_binding): binding(_binding), outer_context(NULL)
{ }
 
RubyValue Context::entry_to_value(const Stack::StackEntry &_entry)
{
  // We take control of StackEntry's memory. See a note in stack.cpp:pop_value about how we should do this better.
 
  switch (_entry.type) {
    case Stack::SE_IDENTIFIER: {
      std::string id = *_entry.identifier;
      delete _entry.identifier;
      return resolve_identifier(id);
    }
 
    case Stack::SE_SYMBOL: {
      std::string symbol = *_entry.symbol;
      delete _entry.symbol;
      return S2V(binding->environment.get_symbol(symbol));
    }
 
    case Stack::SE_INTEGER: return F2V(_entry.integer);
    case Stack::SE_OBJECT: return O2V(_entry.object);
    default:
      throw WorldException(binding, binding->environment.RuntimeError, "Context: trying to convert non-id/sym/int to RValue");
  }
}
 
RubyValue Context::resolve_local(const std::string &_identifier)
{
  std::map<std::string, RubyValue>::const_iterator iter = binding->locals.find(_identifier);
  if (iter != binding->locals.end())
    return iter->second;
 
  if (outer_context)
    return outer_context->resolve_local(_identifier);
 
  throw CannotFindLocalError();
}
 
RubyValue Context::resolve_identifier(const std::string &_identifier)
{
  if (_identifier[0] == '$') {
    if (binding->environment.global_exists(_identifier))
      return binding->environment.get_global_by_name(_identifier);
    return binding->environment.NIL;
  } else if (_identifier[0] == '@') {
    if (_identifier[1] == '@') {
      // @@var
      RubyClass *c = binding->context.get_class(binding->environment);
      if (c->has_class_variable(_identifier))
return c->get_class_variable(_identifier);
      throw WorldException(binding, binding->environment.NameError, "uninitialized class variable " + _identifier + " in " + c->get_name());
    } else {
      // @var
      if (binding->context.has_instance(_identifier))
return binding->context.get_instance(_identifier);
      return binding->environment.NIL;
    }
  } else if (isupper(_identifier[0]))
    return resolve_constant(_identifier);
 
  if (_identifier == "self")
    return binding->context;
 
  try {
    return resolve_local(_identifier);
  } catch (CannotFindLocalError)
  { }
 
  // how about globals?
  if (isalpha(_identifier[0]) && isupper(_identifier[0]))
    try {
      return binding->environment.get_global_by_name(_identifier);
    } catch (CannotFindGlobalError)
    { }
 
  // methods
  try {
    return get_method(_identifier)->call(binding, binding->context, std::vector<RubyValue>());
  } catch (std::exception) // XXX: better defined exception.
  { }
 
  throw WorldException(binding, binding->environment.NameError, "undefined local variable or method `" + _identifier + '\'');
}
 
RubyValue Context::resolve_constant(const std::string &_identifier)
{
  // XXX ??? 这两个有什么不同的??
  // return binding->context.get_class(binding->environment)->resolve_constant(binding, _identifier, true);
  return binding->def_target->resolve_constant(binding, _identifier, true);
}
 
const linked_ptr<RubyMethod> &Context::get_method(const std::string &_name)
{
  return binding->context.get_method(binding, _name);
}
 
void Context::assign(const std::string &_name, RubyValue _value)
{
  if (_name[0] == '$')
    try {
      return binding->environment.set_global_by_name(_name, _value);
    } catch (CannotChangeReadonlyError) {
      throw WorldException(binding, binding->environment.NameError, _name + " is a read-only variable");
    }
  else if (_name[0] == '@') {
    if (_name[1] == '@')
      binding->context.get_class(binding->environment)->set_class_variable(_name, _value);
    else
      binding->context.set_instance(_name, _value);
    return;
  } else if (isupper(_name[0]))
    // XXX don't use def_target?
    return binding->def_target->set_constant(_name, _value);
 
  if (assign_if_exists(_name, _value))
    return;
 
  if (binding->locals.find(_name) != binding->locals.end()) {
    // XXX We're going to overwrite something.
    // XXX GC concerns?
  }
 
  binding->locals[_name] = _value;
}
 
bool Context::assign_if_exists(const std::string &_name, RubyValue _value)
{
  if (binding->locals.find(_name) != binding->locals.end()) {
    // overwriting.
    binding->locals[_name] = _value;
    return true;
  }
 
  if (outer_context)
    return outer_context->assign_if_exists(_name, _value);
 
  return false;
}