-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathInterpreter.cs
367 lines (309 loc) · 11.1 KB
/
Interpreter.cs
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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
using System;
using System.Collections.Generic;
// group members: Peter Zhang, Madeline Moore, Cara Cannarozzi
// Crafting Interpreters book by Robert Nystrom used as a reference
// https://craftinginterpreters.com/contents.html
namespace LoxInterpreter
{
// interprets expressions
public class Interpreter : Expr.IVisitor<object>, Stmt.IVisitor<object>
{
private static readonly Environment Globals = new Environment();
private readonly Dictionary<Expr, int> locals = new Dictionary<Expr, int>();
private Environment environment = Globals;
// defines native clock function (constructor)
public Interpreter()
{
Globals.Define("clock", new Clock());
}
// executes block statement
public object VisitBlockStmt(Stmt.Block stmt)
{
ExecuteBlock(stmt.statements, new Environment(environment));
return null;
}
// evaluates inner expression
public object VisitExpressionStmt(Stmt.Expression stmt)
{
Evaluate(stmt.expression);
return null;
}
// evaluates function statements
public object VisitFunctionStmt(Stmt.Function stmt)
{
var function = new LoxFunction(stmt, environment);
environment.Define(stmt.name.lexeme, function);
return null;
}
// evaluates if statements
public object VisitIfStmt(Stmt.If stmt)
{
if (IsTruthy(Evaluate(stmt.condition)))
Execute(stmt.thenBranch);
else if (stmt.elseBranch != null) Execute(stmt.elseBranch);
return null;
}
// evaluates inner expression, prints to console
public object VisitPrintStmt(Stmt.Print stmt)
{
var value = Evaluate(stmt.expression);
Console.WriteLine(Stringify(value));
return null;
}
// evaluates return statement of function
public object VisitReturnStmt(Stmt.Return stmt)
{
object value = null;
if (stmt.Value != null)
value = Evaluate(stmt.Value);
throw new Return(value);
}
// evaluates variable or sets variable to null
// (if the variable already exists or not, respectively)
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;
}
// evaluates while statement
public object VisitWhileStmt(Stmt.While stmt)
{
while (IsTruthy(Evaluate(stmt.condition))) Execute(stmt.body);
return null;
}
// evaluates assignment expressions
public object VisitAssignExpr(Expr.Assign expr)
{
var value = Evaluate(expr.value);
if (locals.ContainsKey(expr))
{
var distance = locals[expr];
environment.AssignAt(distance, expr.name, value);
}
else
{
Globals.Assign(expr.name, value);
}
return value;
}
// evaluates binary operators
public object VisitBinaryExpr(Expr.Binary expr)
{
var left = Evaluate(expr.left);
var right = Evaluate(expr.right);
switch (expr.op.type)
{
// equality operators
case TokenType.BANG_EQUAL:
return !IsEqual(left, right);
case TokenType.EQUAL_EQUAL:
return IsEqual(left, right);
// comparison operators
case TokenType.GREATER:
CheckNumberOperands(left, right);
return (double) left > (double) right;
case TokenType.GREATER_EQUAL:
CheckNumberOperands(left, right);
return (double) left >= (double) right;
case TokenType.LESS:
CheckNumberOperands(left, right);
return (double) left < (double) right;
case TokenType.LESS_EQUAL:
CheckNumberOperands(left, right);
return (double) left <= (double) right;
// arithmetic binary operators
case TokenType.MINUS:
CheckNumberOperands(left, right);
return (double) left - (double) right;
case TokenType.PLUS:
// handles that + can be used to concatenate strings or add numbers
if (left is double && right is double) return (double) left + (double) right;
if (left is string && right is string) return (string) left + (string) right;
break;
case TokenType.SLASH:
CheckNumberOperands(left, right);
return (double) left / (double) right;
case TokenType.STAR:
CheckNumberOperands(left, right);
return (double) left * (double) right;
}
return null;
}
// evaluates function calls
public object VisitCallExpr(Expr.Call expr)
{
var callee = Evaluate(expr.callee);
var arguments = new List<object>();
foreach (var argument in expr.arguments)
arguments.Add(Evaluate(argument));
var function = (ILoxCallable) callee;
return function.Call(this, arguments);
}
// evaluates get expressions (getters)
public object VisitGetExpr(Expr.Get expr)
{
return null;
}
// evaluates when there are explicit parentheses in an expression
public object VisitGroupingExpr(Expr.Grouping expr)
{
return Evaluate(expr.expression);
}
// convert tree nodes into runtime values
public object VisitLiteralExpr(Expr.Literal expr)
{
return expr.value;
}
// evaluates or statements from left to right
public object VisitLogicalExpr(Expr.Logical expr)
{
var left = Evaluate(expr.left);
if (expr.op.type == TokenType.OR)
{
if (IsTruthy(left)) return left;
}
else
{
if (!IsTruthy(left)) return left;
}
return Evaluate(expr.right);
}
// evaluates set expressions (setters)
public object VisitSetExpr(Expr.Set expr)
{
return null;
}
// evaluates unary expressions
public object VisitUnaryExpr(Expr.Unary expr)
{
// evaluate operand expression
var right = Evaluate(expr.right);
// apply unary operator to result of evaluated expression
switch (expr.op.type)
{
case TokenType.BANG:
return !IsTruthy(right);
case TokenType.MINUS:
CheckNumberOperand(right);
return -(double) right;
}
return null;
}
// sends variable to environment with its definition
public object VisitVariableExpr(Expr.Variable expr)
{
return LookUpVariable(expr.name, expr);
}
// evaluates each statement in given list of statements
public void Interpret(List<Stmt> statements)
{
foreach (var statement in statements) Execute(statement);
}
// evaluates expression contained inside of parentheses
private object Evaluate(Expr expr)
{
return expr.Accept(this);
}
// similar to Evaluate() but for statements instead of expressions
private void Execute(Stmt stmt)
{
stmt.Accept(this);
}
/// <summary>
/// resolves expressions at depth within the environment
/// </summary>
/// <param name="expr"></param>
/// <param name="depth"></param>
public void Resolve(Expr expr, int depth)
{
locals.Add(expr, depth);
}
/// <summary>
/// executes list of statements in context of env
/// </summary>
/// <param name="statements"></param>
/// <param name="env"></param>
public void ExecuteBlock(List<Stmt> statements, Environment env)
{
var previous = environment;
try
{
environment = env;
foreach (var statement in statements) Execute(statement);
}
finally
{
environment = previous;
}
}
// looks up distance in environment, gets variable (if it exists)
private object LookUpVariable(Token name, Expr expr)
{
int distance;
if (locals.ContainsKey(expr))
{
distance = locals[expr];
return environment.GetAt(distance, name.lexeme);
}
return Globals.Get(name);
}
// checks the operand type for unary expressions
private void CheckNumberOperand(object operand)
{
if (operand is double)
return;
}
// checks the operand type for binary expressions
private void CheckNumberOperands(object left, object right)
{
if (left is double && right is double) return;
}
/// <summary>
/// determines truthiness of an object
/// </summary>
/// <param name="obj"></param>
/// <returns>boolean truthy value of an object</returns>
private bool IsTruthy(object obj)
{
if (obj == null) return false;
if (obj is bool) return (bool) obj;
return true;
}
/// <summary>
/// checks if two objects are equal
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns>boolean equality value of a and b</returns>
private bool IsEqual(object a, object b)
{
if (a == null && b == null) return true;
if (a == null) return false;
return a.Equals(b);
}
// converts lox object to string
private string Stringify(object obj)
{
// handles if null
if (obj == null) return "nil";
// handles if number
if (obj is double)
{
var text = obj.ToString();
if (text.EndsWith(".0")) text = text.Substring(0, text.Length - 2);
return text;
}
// handles if bool
if (obj is bool)
{
if ((bool) obj)
return "true";
return "false";
}
// handles if anything else
return obj.ToString();
}
}
}