-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathInterpreter.cs
More file actions
278 lines (239 loc) · 8.96 KB
/
Copy pathInterpreter.cs
File metadata and controls
278 lines (239 loc) · 8.96 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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
namespace cslox {
using System.Reflection.Metadata;
using static TokenType;
public class Interpreter : Expr.Visitor<object>, Stmt.Visitor<Object> {
public readonly Environment globals;
private Environment environment;
private readonly Dictionary<Expr, int> locals = new();
public Interpreter() {
globals = new();
environment = globals;
globals.Define("clock", new ClockCallable());
}
private class ClockCallable : LoxCallable {
public int Arity() {
return 0;
}
public object Call(Interpreter interpreter, List<object> args) {
return (double)DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
}
public override string ToString() {
return "<native fn>";
}
}
public void Interpret(List<Stmt> stmts) {
try {
foreach(Stmt stmt in stmts) {
Execute(stmt);
}
}
catch(RuntimeError error) {
CSLox.RuntimeError(error);
}
}
private string Stringify(object obj) {
if(obj == null) return "nil";
if(obj is double) {
string text = obj.ToString();
if(text.EndsWith(".0")) {
text = text.Substring(0, text.Length - 2);
}
return text;
}
return obj.ToString();
}
public object VisitLiteralExpr(Expr.Literal expr) {
return expr.value;
}
public object VisitGroupingExpr(Expr.Grouping expr) {
return Evaluate(expr.expression);
}
public object VisitUnaryExpr(Expr.Unary expr) {
object right = Evaluate(expr.right);
switch(expr.op.type) {
case BANG:
return !IsTruthy(right);
case MINUS:
CheckNumberOperand(expr.op, right);
return -(double)right;
}
// Unreachable.
return null;
}
public object VisitBinaryExpr(Expr.Binary expr) {
object left = Evaluate(expr.left);
object right = Evaluate(expr.right);
switch(expr.op.type) {
case GREATER:
CheckNumberOperands(expr.op, left, right);
return (double)left > (double)right;
case GREATER_EQUAL:
CheckNumberOperands(expr.op, left, right);
return (double)left >= (double)right;
case LESS:
CheckNumberOperands(expr.op, left, right);
return (double)left < (double)right;
case LESS_EQUAL:
CheckNumberOperands(expr.op, left, right);
return (double)left <= (double)right;
case BANG_EQUAL:
return !IsEqual(left, right);
case EQUAL_EQUAL:
return IsEqual(left, right);
case MINUS:
CheckNumberOperands(expr.op, left, right);
return (double)left - (double)right;
case PLUS:
if(left is double && right is double) {
return (double)left + (double)right;
}
if(left is string && right is string) {
return (string)left + (string)right;
}
throw new RuntimeError(expr.op, "Operands must be two numbers or two strings.");
case SLASH:
CheckNumberOperands(expr.op, left, right);
return (double)left / (double)right;
case STAR:
CheckNumberOperands(expr.op, left, right);
return (double)left * (double)right;
}
// Unreachable.
return null;
}
public object VisitCallExpr(Expr.Call expr) {
object callee = Evaluate(expr.callee);
List<object> args = new();
foreach(Expr arg in expr.arguments) {
args.Add(Evaluate(arg));
}
if(!(callee is LoxCallable)) {
throw new RuntimeError(expr.paren, "Can only call functions and class constructors.");
}
LoxCallable function = (LoxCallable) callee;
if(args.Count != function.Arity()) {
throw new RuntimeError(expr.paren, "Expected " + function.Arity() + " arguments but got " + args.Count + ".");
}
return function.Call(this, args);
}
public object VisitVariableExpr(Expr.Variable expr) {
return LookupVariable(expr.name, expr);
}
public object VisitAssignExpr(Expr.Assign expr) {
object value = Evaluate(expr.value);
int distance = locals[expr];
if(distance != null) {
environment.AssignAt(distance, expr.name, value);
}
else {
globals.Assign(expr.name, value);
}
return value;
}
public object VisitLogicalExpr(Expr.Logical expr) {
object left = Evaluate(expr.left);
if(expr.op.type == OR) {
if(IsTruthy(left)) return left;
}
else {
if(!IsTruthy(left)) return left;
}
return Evaluate(expr.right);
}
public object VisitExpressionStmt(Stmt.Expression stmt) {
Evaluate(stmt.expression);
return null;
}
public object VisitWhileStmt(Stmt.While stmt) {
while(IsTruthy(Evaluate(stmt.condition))) {
Execute(stmt.body);
}
return null;
}
public object VisitPrintStmt(Stmt.Print stmt) {
object value = Evaluate(stmt.expression);
Console.WriteLine(Stringify(value));
return null;
}
public object VisitVarStmt(Stmt.Var stmt) {
Object value = null;
if(stmt.initializer != null) {
value = Evaluate(stmt.initializer);
}
environment.Define(stmt.name.lexeme, value);
return null;
}
public object VisitIfStmt(Stmt.If stmt) {
if(IsTruthy(Evaluate(stmt.condition))) {
Execute(stmt.thenBranch);
}
else if(stmt.elseBranch != null) {
Execute(stmt.elseBranch);
}
return null;
}
public object VisitBlockStmt(Stmt.Block stmt) {
ExecuteBlock(stmt.statements, new Environment(environment));
return null;
}
public object VisitFunctionStmt(Stmt.Function stmt) {
LoxFunction function = new(stmt, environment);
environment.Define(stmt.name.lexeme, function);
return null;
}
public object VisitReturnStmt(Stmt.Return stmt) {
object value = null;
if(stmt.value != null) value = Evaluate(stmt.value);
throw new Return(value);
}
private object LookupVariable(Token name, Expr expr) {
int distance = locals[expr];
if(distance != null) {
return environment.GetAt(distance, name.lexeme);
}
else {
return globals.Get(name);
}
}
public void Resolve(Expr expr, int depth) {
locals[expr] = depth;
}
private object Evaluate(Expr expr) {
return expr.Accept<object>(this);
}
public void Execute(Stmt stmt) {
stmt.Accept<object>(this);
}
public void ExecuteBlock(List<Stmt> statements, Environment environment) {
Environment previous = this.environment;
try {
this.environment = environment;
foreach(Stmt stmt in statements) {
Execute(stmt);
}
}
finally {
this.environment = previous;
}
}
private bool IsTruthy(object obj) {
if(obj == null) return false;
if(obj is bool) return (bool)obj;
return true;
}
private bool IsEqual(object a, object b) {
// nil is only equal to nil.
if(a == null && b == null) return true;
if(a == null) return false;
return a.Equals(b);
}
private void CheckNumberOperand(Token op, object operand) {
if(operand is double) return;
throw new RuntimeError(op, "Operand must be a number.");
}
private void CheckNumberOperands(Token op, object left, object right) {
if(left is double && right is double) return;
throw new RuntimeError(op, "Operands must be numbers.");
}
}
}