using System; using System.Collections.Immutable; using AST = Finn.AST; namespace Finn; public class Interpreter : AST.IExprVisitor { 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>.Empty, }; public required ImmutableDictionary> Fields { get; init; } public Record Update(string name, object value) { ImmutableStack? 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? values; if (!Fields.TryGetValue(name, out values)) { throw new ArgumentException($"no such field: {name}"); } return values.Peek(); } public Record Remove(string name) { ImmutableStack? 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.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 "); 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(); } }