diff --git a/Interpreter.cs b/Interpreter.cs index 987f9c5..a9e049f 100644 --- a/Interpreter.cs +++ b/Interpreter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using AST = Finn.AST; @@ -27,13 +28,47 @@ public class Interpreter : AST.IExprVisitor } } + private class List + { + public required ImmutableList Items { get; init; } + + public static List Empty = new List { Items = ImmutableList.Empty }; + + public object this[double i] + { + get { return Items[(int)i]; } + } + + public bool IsEmpty + { + get { return Items.IsEmpty; } + } + + public List Add(object item) + { + return new List { Items = Items.Add(item) }; + } + + public override string ToString() + { + System.IO.StringWriter sw = new System.IO.StringWriter(); + sw.Write("["); + foreach (var item in this.Items) + { + sw.Write($" {item},"); + } + sw.Write(" ]"); + return sw.ToString(); + } + } + private class Record { public static Record Empty = new Record { - Fields = ImmutableDictionary>.Empty, + Fields = ImmutableSortedDictionary>.Empty, }; - public required ImmutableDictionary> Fields { get; init; } + public required ImmutableSortedDictionary> Fields { get; init; } public Record Update(string name, object value) { @@ -77,6 +112,21 @@ public class Interpreter : AST.IExprVisitor var values = Fields.GetValueOrDefault(name, ImmutableStack.Empty); return new Record { Fields = Fields.SetItem(name, values.Push(value)) }; } + + public override string ToString() + { + System.IO.StringWriter sw = new System.IO.StringWriter(); + sw.Write("{"); + foreach ((var label, var values) in this.Fields) + { + foreach (var value in values) + { + sw.Write($" {label} = {value},"); + } + } + sw.Write(" }"); + return sw.ToString(); + } } private record Variant(string Tag, object? Value) @@ -120,9 +170,21 @@ public class Interpreter : AST.IExprVisitor return expr.accept(this); } - private void checkNumberOperand(Token op, Object operand) + private List checkListOperand(Token op, Object operand) { - if (operand is double) return; + if (operand is List l) return l; + throw new RuntimeError(op, "Operand must be a record."); + } + + private Record checkRecordOperand(Token op, Object operand) + { + if (operand is Record r) return r; + throw new RuntimeError(op, "Operand must be a record."); + } + + private double checkNumberOperand(Token op, Object operand) + { + if (operand is double d) return d; throw new RuntimeError(op, "Operand must be a number."); } @@ -138,6 +200,15 @@ public class Interpreter : AST.IExprVisitor throw new RuntimeError(op, "Operands must be strings."); } + private Variant checkBoolOperand(Token op, object operand) + { + if (operand is Variant v) + { + if (v == Variant.True || v == Variant.False) return v; + } + throw new RuntimeError(op, "Operand must be ."); + } + public object visitBinaryExpr(AST.Binary expr) { var left = evaluate(expr.Left); @@ -202,7 +273,6 @@ public class Interpreter : AST.IExprVisitor public object visitGroupingExpr(AST.Grouping expr) { return evaluate(expr.Expression); - throw new System.NotImplementedException(); } public object visitIdentifierExpr(AST.Identifier expr) @@ -212,12 +282,31 @@ public class Interpreter : AST.IExprVisitor public object visitIfExpr(AST.If expr) { - throw new System.NotImplementedException(); + var cond = evaluate(expr.Condition); + // TODO Maybe I should token info in the AST. + var vb = checkBoolOperand(new Token(TokenType.If, "if", null, 1), cond); + if (vb == Variant.True) + { + return evaluate(expr.Then); + } + else + { + return evaluate(expr.Else); + } } public object visitIndexerExpr(AST.Indexer expr) { - throw new System.NotImplementedException(); + // TODO use real token + var tok = new Token(TokenType.LBracket, "[", null, 1); + var left = checkListOperand(tok, evaluate(expr.Left)); + var index = checkNumberOperand(tok, evaluate(expr.Index)); + try + { + var item = left[index]; + return new Variant("some", item); + } + catch { return new Variant("nothing", null); } } public object visitLetExpr(AST.Let expr) @@ -227,7 +316,20 @@ public class Interpreter : AST.IExprVisitor public object visitListExpr(AST.List expr) { - throw new System.NotImplementedException(); + // TODO use real token + var tok = new Token(TokenType.LBracket, "[", null, 1); + List l = List.Empty; + foreach (var itemExpr in expr.Elements) + { + var item = evaluate(itemExpr); + if (!l.IsEmpty) + { + try { checkTypesEqual(l[0], item); } + catch { throw new RuntimeError(tok, "List items must all have same type."); } + } + l = l.Add(item); + } + return l; } public object visitLiteralExpr(AST.Literal expr) @@ -237,17 +339,79 @@ public class Interpreter : AST.IExprVisitor public object visitRecordExpr(AST.Record expr) { - throw new System.NotImplementedException(); + // TODO use real token + Token tok = new Token(TokenType.LBrace, "{", null, 1); + Record rec = Record.Empty; + if (expr.Base != null) + { + var baseRecValue = evaluate(expr.Base.Value); + if (baseRecValue is not Record) + { + throw new RuntimeError(tok, "Base record must be a record."); + } + var baseRec = (Record)baseRecValue; + + // Updates + HashSet updateLabels = new HashSet(); + foreach (AST.Field update in expr.Base.Updates) + { + var label = update.Name.Value; + if (updateLabels.Contains(label)) + { + throw new RuntimeError(tok, "Record updates must be to unique fields."); + } + updateLabels.Add(label); + if (update.Value == null) throw new NotImplementedException(); + try + { + baseRec = baseRec.Update(label, evaluate(update.Value)); + } + catch + { + throw new RuntimeError(tok, "Field update must have same type as previous value."); + } + } + rec = baseRec; + } + + // Extensions + HashSet extLabels = new HashSet(); + foreach (AST.Field extension in expr.Extensions) + { + var label = extension.Name.Value; + if (extLabels.Contains(label)) + { + throw new RuntimeError(tok, "Record extensions must have unique field names."); + + } + extLabels.Add(label); + if (extension.Value == null) throw new NotImplementedException(); + rec = rec.Extend(label, evaluate(extension.Value)); + } + + return rec; } public object visitSelectorExpr(AST.Selector expr) { - throw new System.NotImplementedException(); + var left = evaluate(expr.Left); + // TODO Use real token. + var tok = new Token(TokenType.Period, ".", null, 1); + var r = checkRecordOperand(tok, left); + try + { + return r.Get(expr.FieldName.Value); + } + catch + { + throw new RuntimeError(tok, "Operand must have selected field."); + } } public object visitSequenceExpr(AST.Sequence expr) { - throw new System.NotImplementedException(); + evaluate(expr.Left); + return evaluate(expr.Right); } public object visitUnaryExpr(AST.Unary expr) @@ -259,18 +423,14 @@ public class Interpreter : AST.IExprVisitor checkNumberOperand(expr.Op, right); return -(double)right; case TokenType.Bang: - if (right is Variant v) + if (checkBoolOperand(expr.Op, right) == Variant.True) { - if (v == Variant.True) - { - return Variant.False; - } - else if (v == Variant.False) - { - return Variant.True; - } + return Variant.False; + } + else + { + return Variant.True; } - throw new RuntimeError(expr.Op, "Boolean must be "); default: // Unreachable throw new Exception($"bad unary op: {expr.Op}"); @@ -279,7 +439,7 @@ public class Interpreter : AST.IExprVisitor public object visitVariantExpr(AST.Variant expr) { - throw new System.NotImplementedException(); + return new Variant(expr.Tag.Value, expr.Argument); } public object visitWhenExpr(AST.When expr)