Evaluate arithmetic expressions

This commit is contained in:
Brandon Dyck 2023-07-06 01:11:00 -06:00
parent 6459e39260
commit b1607eebc6
2 changed files with 302 additions and 2 deletions

289
Interpreter.cs Normal file
View File

@ -0,0 +1,289 @@
using System;
using System.Collections.Immutable;
using AST = Finn.AST;
namespace Finn;
public class Interpreter : AST.IExprVisitor<object>
{
public class RuntimeError : Exception
{
public readonly Token Token;
internal RuntimeError(Token token, String message) : base(message)
{
Token = token;
}
}
private static void checkTypesEqual(object a, object b)
{
var aType = a.GetType();
var bType = b.GetType();
if (aType != bType)
{
throw new Exception("Type mismatch: {aType} != {bType}.");
}
}
private class Record
{
public static Record Empty = new Record
{
Fields = ImmutableDictionary<string, ImmutableStack<object>>.Empty,
};
public required ImmutableDictionary<string, ImmutableStack<object>> Fields { get; init; }
public Record Update(string name, object value)
{
ImmutableStack<object>? values;
if (!Fields.TryGetValue(name, out values))
{
throw new ArgumentException($"no such field: {name}");
}
checkTypesEqual(value, values.Peek());
return new Record { Fields = Fields.SetItem(name, values!.Pop().Push(value)) };
}
public object Get(string name)
{
ImmutableStack<object>? values;
if (!Fields.TryGetValue(name, out values))
{
throw new ArgumentException($"no such field: {name}");
}
return values.Peek();
}
public Record Remove(string name)
{
ImmutableStack<object>? values;
if (!Fields.TryGetValue(name, out values))
{
throw new ArgumentException($"no such field: {name}");
}
var value = values.Peek();
var popped = values.Pop();
if (popped.IsEmpty)
{
return new Record { Fields = Fields.Remove(name) };
}
return new Record { Fields = Fields.SetItem(name, popped) };
}
public Record Extend(string name, object value)
{
var values = Fields.GetValueOrDefault(name, ImmutableStack<object>.Empty);
return new Record { Fields = Fields.SetItem(name, values.Push(value)) };
}
}
private record Variant(string Tag, object? Value)
{
public static readonly Variant True = new Variant("true", null);
public static readonly Variant False = new Variant("false", null);
public static Variant FromBool(bool b)
{
return b ? True : False;
}
public bool IsEmpty { get { return Value == null; } }
public override string ToString()
{
if (Value == null)
{
return $"`{Tag}";
}
else
{
return $"`{Tag}({Value})";
}
}
}
public void Interpret(AST.Expr expression)
{
try
{
var value = evaluate(expression);
Console.WriteLine(value);
}
catch (RuntimeError err)
{
Program.runtimeError(err);
}
}
private object evaluate(AST.Expr expr)
{
return expr.accept(this);
}
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.");
}
private void checkStringOperands(Token op, object left, object right)
{
if (left is string && right is string) return;
throw new RuntimeError(op, "Operands must be strings.");
}
public object visitBinaryExpr(AST.Binary expr)
{
var left = evaluate(expr.Left);
var right = evaluate(expr.Right);
switch (expr.Op.type)
{
case TokenType.Minus:
checkNumberOperands(expr.Op, left, right);
return (double)left - (double)right;
case TokenType.Plus:
checkNumberOperands(expr.Op, left, right);
return (double)left + (double)right;
case TokenType.Slash:
checkNumberOperands(expr.Op, left, right);
return (double)left / (double)right;
case TokenType.Asterisk:
checkNumberOperands(expr.Op, left, right);
return (double)left * (double)right;
case TokenType.PlusPlus:
checkStringOperands(expr.Op, left, right);
return (string)left + (string)right;
case TokenType.Greater:
checkNumberOperands(expr.Op, left, right);
return Variant.FromBool((double)left > (double)right);
case TokenType.GreaterEqual:
checkNumberOperands(expr.Op, left, right);
return Variant.FromBool((double)left >= (double)right);
case TokenType.Less:
checkNumberOperands(expr.Op, left, right);
return Variant.FromBool((double)left < (double)right);
case TokenType.LessEqual:
checkNumberOperands(expr.Op, left, right);
return Variant.FromBool((double)left <= (double)right);
case TokenType.BangEqual:
return Variant.FromBool((left, right) switch
{
(String l, String r) => l != r,
(double l, double r) => l != r,
_ => throw new ArgumentException(),
});
case TokenType.DoubleEqual:
return Variant.FromBool((left, right) switch
{
(String l, String r) => l == r,
(double l, double r) => l == r,
_ => throw new ArgumentException(),
});
}
throw new ArgumentException($"bad binary op: {expr.Op}");
}
public object visitCallExpr(AST.Call expr)
{
throw new System.NotImplementedException();
}
public object visitGroupingExpr(AST.Grouping expr)
{
return evaluate(expr.Expression);
throw new System.NotImplementedException();
}
public object visitIdentifierExpr(AST.Identifier expr)
{
throw new System.NotImplementedException();
}
public object visitIfExpr(AST.If expr)
{
throw new System.NotImplementedException();
}
public object visitIndexerExpr(AST.Indexer expr)
{
throw new System.NotImplementedException();
}
public object visitLetExpr(AST.Let expr)
{
throw new System.NotImplementedException();
}
public object visitListExpr(AST.List expr)
{
throw new System.NotImplementedException();
}
public object visitLiteralExpr(AST.Literal expr)
{
return expr.Value;
}
public object visitRecordExpr(AST.Record expr)
{
throw new System.NotImplementedException();
}
public object visitSelectorExpr(AST.Selector expr)
{
throw new System.NotImplementedException();
}
public object visitSequenceExpr(AST.Sequence expr)
{
throw new System.NotImplementedException();
}
public object visitUnaryExpr(AST.Unary expr)
{
object right = evaluate(expr.Right);
switch (expr.Op.type)
{
case TokenType.Minus:
checkNumberOperand(expr.Op, right);
return -(double)right;
case TokenType.Bang:
if (right is Variant v)
{
if (v == Variant.True)
{
return Variant.False;
}
else if (v == Variant.False)
{
return Variant.True;
}
}
throw new RuntimeError(expr.Op, "Boolean must be <true, false>");
default:
// Unreachable
throw new Exception($"bad unary op: {expr.Op}");
}
}
public object visitVariantExpr(AST.Variant expr)
{
throw new System.NotImplementedException();
}
public object visitWhenExpr(AST.When expr)
{
throw new System.NotImplementedException();
}
}

View File

@ -7,7 +7,9 @@ namespace Finn;
class Program class Program
{ {
private static readonly Interpreter interpreter = new Interpreter();
static bool hadError = false; static bool hadError = false;
static bool hadRuntimeError = true;
static void Main(string[] args) static void Main(string[] args)
{ {
@ -30,6 +32,9 @@ class Program
{ {
var src = File.ReadAllText(path); var src = File.ReadAllText(path);
run(src); run(src);
if (hadError) Environment.Exit(65);
if (hadRuntimeError) Environment.Exit(70);
} }
static void run(string src) static void run(string src)
@ -39,13 +44,13 @@ class Program
Parser parser = new Parser(tokens); Parser parser = new Parser(tokens);
Expr? expression = parser.parse(); Expr? expression = parser.parse();
if (hadError || expression == null) if (hadError)
{ {
hadError = false; hadError = false;
return; return;
} }
Console.WriteLine(expression); interpreter.Interpret(expression!);
} }
static void runPrompt() static void runPrompt()
@ -79,6 +84,12 @@ class Program
} }
} }
public static void runtimeError(Interpreter.RuntimeError err)
{
Console.Error.WriteLine($"{err.Message}\n[line {err.Token.line}]");
hadRuntimeError = true;
}
static void report(int line, String where, String message) static void report(int line, String where, String message)
{ {
Console.WriteLine($"[line {line}] Error{where}: {message}"); Console.WriteLine($"[line {line}] Error{where}: {message}");