Evaluate all non-binding expressions
This commit is contained in:
parent
b1607eebc6
commit
f9df52b5f9
204
Interpreter.cs
204
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<object>
|
||||
}
|
||||
}
|
||||
|
||||
private class List
|
||||
{
|
||||
public required ImmutableList<object> Items { get; init; }
|
||||
|
||||
public static List Empty = new List { Items = ImmutableList<object>.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<string, ImmutableStack<object>>.Empty,
|
||||
Fields = ImmutableSortedDictionary<string, ImmutableStack<object>>.Empty,
|
||||
};
|
||||
public required ImmutableDictionary<string, ImmutableStack<object>> Fields { get; init; }
|
||||
public required ImmutableSortedDictionary<string, ImmutableStack<object>> Fields { get; init; }
|
||||
|
||||
public Record Update(string name, object value)
|
||||
{
|
||||
@ -77,6 +112,21 @@ public class Interpreter : AST.IExprVisitor<object>
|
||||
var values = Fields.GetValueOrDefault(name, ImmutableStack<object>.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<object>
|
||||
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<object>
|
||||
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 <true,false>.");
|
||||
}
|
||||
|
||||
public object visitBinaryExpr(AST.Binary expr)
|
||||
{
|
||||
var left = evaluate(expr.Left);
|
||||
@ -202,7 +273,6 @@ public class Interpreter : AST.IExprVisitor<object>
|
||||
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<object>
|
||||
|
||||
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<object>
|
||||
|
||||
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<object>
|
||||
|
||||
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<string> updateLabels = new HashSet<string>();
|
||||
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<string> extLabels = new HashSet<string>();
|
||||
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<object>
|
||||
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 <true, false>");
|
||||
default:
|
||||
// Unreachable
|
||||
throw new Exception($"bad unary op: {expr.Op}");
|
||||
@ -279,7 +439,7 @@ public class Interpreter : AST.IExprVisitor<object>
|
||||
|
||||
public object visitVariantExpr(AST.Variant expr)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
return new Variant(expr.Tag.Value, expr.Argument);
|
||||
}
|
||||
|
||||
public object visitWhenExpr(AST.When expr)
|
||||
|
Loading…
Reference in New Issue
Block a user