-
Notifications
You must be signed in to change notification settings - Fork 0
/
interpreter.rb
150 lines (128 loc) · 3.23 KB
/
interpreter.rb
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
require "parser"
require "runtime"
class Interpreter
def initialize
@parser = Parser.new
end
def eval(code)
@parser.parse(code).eval(Runtime)
end
end
class Nodes
# this method is the "interpreter" part of the language
# all nodes know how to eval # itself and returns the result
# of its evaluation by implementing the "eval" method
# the "context" variable is the environment in which the node is evaluated
# local variables, current class, etc
def eval(context)
return_value = nil
nodes.each do |node|
return_value = node.eval(context)
end
# the last value evaluated in a method is the return value
# or nil if none
return_value || Runtime["nil"]
end
end
class NumberNode
def eval(context)
# access the Runtime to create a new instance of the Number class
Runtime["Number"].new_with_value(value)
end
end
class StringNode
def eval(context)
Runtime["String"].new_with_value(value)
end
end
class TrueNode
def eval(context)
Runtime["true"]
end
end
class FalseNode
def eval(context)
Runtime["false"]
end
end
class NilNode
def eval(context)
Runtime["nil"]
end
end
class CallNode
def eval(context)
# if there's no receiver and the method name is the name of a local variable
# then it's a local variable access
# this trick allows us to skip the () when calling a method
if receiver.nil? && context.locals[method] && arguments.empty?
context.locals[method]
# method call
else
if receiver
value = receiver.eval(context)
else
# in case there's no receiver, default to self, calling "print" is like
# "self.print"
value = context.current_self
end
eval_arguments = arguments.map { |arg| arg.eval(context) }
value.call(method, eval_arguments)
end
end
end
class GetConstantNode
def eval(context)
context[name]
end
end
class SetConstantNode
def eval(context)
context[name] = value.eval(context)
end
end
class SetLocalNode
def eval(context)
context.locals[name] = value.eval(context)
end
end
class DefNode
def eval(context)
# defining a method is adding a method to the current class
method = ToyMethod.new(params, body)
context.current_class.runtime_methods[name] = method
end
end
class ClassNode
def eval(context)
# try to locate the class. allows reopening classes to add methods
toy_class = context[name]
unless toy_class # class does not exist yet
toy_class = ToyClass.new
# register the class as a constant in the runtime
context[name] = toy_class
end
# evaluate the body of the class in its context
# providing a custom context allows to control
# where methods are added when defined with the def keyword
# in this case, we add them to the newly created class
class_context = Context.new(toy_class, toy_class)
body.eval(class_context)
toy_class
end
end
class IfNode
def eval(context)
# turn the condition node into a Ruby value to use Ruby's "if" control structure
if condition.eval(context).ruby_value
body.eval(context)
end
end
end
def WhileNode
def eval(context)
while @condition.eval(context).ruby_value
@body.eval(context)
end
end
end