Wire up resolver to interpreter

This commit is contained in:
Brandon Dyck 2023-10-04 17:13:48 -06:00
parent 5168890ea0
commit 6e88a41b72
4 changed files with 283 additions and 241 deletions

View File

@ -64,6 +64,20 @@ public class Env
} }
} }
} }
public Env Ancestor(int distance)
{
Env? curr = this;
for (int i = 0; i < distance; i++)
{
curr = curr.enclosing;
if (curr is null)
{
throw new IndexOutOfRangeException();
}
}
return curr;
}
} }
public interface Callable public interface Callable
@ -302,14 +316,16 @@ public class Interpreter : AST.IExprVisitor<Env, object>
binding.Params[i].accept((arguments[i], env), new PatternBinder()); binding.Params[i].accept((arguments[i], env), new PatternBinder());
} }
return interpreter.evaluate(env, binding.Value); return interpreter.evaluate(env, binding.Value);
// throw new NotImplementedException();
} }
} }
protected internal readonly Env Globals = new Env(); protected internal readonly Env Globals = new Env();
private readonly Resolver Resolver;
public Interpreter() public Interpreter(Resolver resolver)
{ {
Resolver = resolver;
Globals["clock"] = new NativeFunction((args) => Globals["clock"] = new NativeFunction((args) =>
{ {
return (double)DateTimeOffset.Now.ToUnixTimeSeconds(); return (double)DateTimeOffset.Now.ToUnixTimeSeconds();
@ -413,14 +429,14 @@ public class Interpreter : AST.IExprVisitor<Env, object>
case TokenType.BangEqual: case TokenType.BangEqual:
return Variant.FromBool((left, right) switch return Variant.FromBool((left, right) switch
{ {
(String l, String r) => l != r, (string l, string r) => l != r,
(double l, double r) => l != r, (double l, double r) => l != r,
_ => throw new ArgumentException(), _ => throw new ArgumentException(),
}); });
case TokenType.DoubleEqual: case TokenType.DoubleEqual:
return Variant.FromBool((left, right) switch return Variant.FromBool((left, right) switch
{ {
(String l, String r) => l == r, (string l, string r) => l == r,
(double l, double r) => l == r, (double l, double r) => l == r,
_ => throw new ArgumentException(), _ => throw new ArgumentException(),
}); });
@ -453,7 +469,15 @@ public class Interpreter : AST.IExprVisitor<Env, object>
public object visitVariableExpr(Env env, AST.Variable expr) public object visitVariableExpr(Env env, AST.Variable expr)
{ {
return env[expr.Value]; var name = (string)expr.Value.Literal!;
if (Resolver.TryResolve(expr, out var distance))
{
return env.Ancestor(distance)[expr.Value];
}
else
{
return Globals[expr.Value];
}
} }
public object visitIfExpr(Env env, AST.If expr) public object visitIfExpr(Env env, AST.If expr)
@ -499,9 +523,11 @@ public class Interpreter : AST.IExprVisitor<Env, object>
} }
catch (Exception e) catch (Exception e)
{ {
var start = e is PatternMismatchException ? var start = e switch
((PatternMismatchException)e).Start : {
binding.Start; PatternMismatchException pme => pme.Start,
_ => binding.Start,
};
throw new RuntimeError(start, e.Message); throw new RuntimeError(start, e.Message);
} }
break; break;

View File

@ -7,7 +7,8 @@ namespace Finn;
class Program class Program
{ {
private static readonly Interpreter interpreter = new Interpreter(); private static readonly Resolver resolver = new Resolver();
private static readonly Interpreter interpreter = new Interpreter(resolver);
static bool hadError = false; static bool hadError = false;
static bool hadRuntimeError = true; static bool hadRuntimeError = true;
@ -41,16 +42,8 @@ class Program
{ {
var scanner = new Scanner(src); var scanner = new Scanner(src);
List<Token> tokens = scanner.scanTokens(); List<Token> tokens = scanner.scanTokens();
// Console.WriteLine("TOKENS\n=======");
// foreach (var token in tokens)
// {
// Console.WriteLine(token);
// }
Parser parser = new Parser(tokens); Parser parser = new Parser(tokens);
Expr? expression = parser.parse(); Expr? expression = parser.parse();
// Console.WriteLine("\nAST\n=======");
// Console.WriteLine(expression);
// Console.WriteLine();
if (hadError) if (hadError)
{ {
@ -58,6 +51,13 @@ class Program
return; return;
} }
resolver.Add(expression!);
if (hadError)
{
hadError = false;
return;
}
interpreter.Interpret(expression!); interpreter.Interpret(expression!);
} }

View File

@ -8,250 +8,267 @@ using ScopeStack = System.Collections.Immutable.ImmutableStack<System.Collection
using Resolutions = System.Collections.Immutable.ImmutableDictionary<Finn.AST.Expr, int>; using Resolutions = System.Collections.Immutable.ImmutableDictionary<Finn.AST.Expr, int>;
using System.Collections.Generic; using System.Collections.Generic;
class Resolver : IExprVisitor<ScopeStack, Resolutions> public class Resolver
{ {
private class NameCollection
{
public ImmutableHashSet<string> Names { get; private set; } = ImmutableHashSet<string>.Empty;
public void Add(Token name)
{
var value = (string)name.Literal!;
if (Names.Contains(value))
{
Program.error(name.Start, $"Duplicate name declared in scope: {name.Lexeme}");
}
else
{
Names = Names.Add(value);
}
}
}
private class PatternNameCollector : IPatternVisitor<NameCollection, ValueTuple>
{
public readonly static PatternNameCollector Instance = new();
private static readonly ValueTuple Void = ValueTuple.Create();
private PatternNameCollector() { }
public ValueTuple visitFieldPatternPattern(NameCollection names, FieldPattern field)
{
if (field.Pattern is null)
{
names.Add(field.Name);
}
else
{
field.Pattern.accept(names, this);
}
return Void;
}
public ValueTuple visitRecordPatternPattern(NameCollection names, RecordPattern record)
{
record.Rest?.accept(names, this);
foreach (var field in record.Fields)
{
field.accept(names, this);
}
return Void;
}
public ValueTuple visitSimplePatternPattern(NameCollection names, SimplePattern simple)
{
if (simple.Identifier is not null)
{
names.Add(simple.Identifier);
}
return Void;
}
public ValueTuple visitVariantPatternPattern(NameCollection names, VariantPattern variant)
{
variant.Argument?.accept(names, this);
return Void;
}
}
private static readonly Resolutions EmptyResolutions = Resolutions.Empty.WithComparers(ReferenceEqualityComparer.Instance); private static readonly Resolutions EmptyResolutions = Resolutions.Empty.WithComparers(ReferenceEqualityComparer.Instance);
public static readonly Resolver Instance = new Resolver();
private Resolver() { } private Resolutions Resolutions = EmptyResolutions;
public bool TryResolve(Expr expr, out int distance)
private static Resolutions AddRange(Resolutions r1, Resolutions r2) => r1.AddRange(r2);
private Resolutions Resolve(ScopeStack scopes, Expr expr)
{ {
return expr.accept(scopes, this); return Resolutions.TryGetValue(expr, out distance);
} }
private Resolutions Resolve(ScopeStack scopes, ImmutableHashSet<string> newScope, Expr value) public void Add(Expr expr)
{ {
return Resolve(scopes.Push(newScope), value); var newResolutions = ResolverBuilder.Instance.Resolve(ScopeStack.Empty, expr);
Resolutions = Resolutions.AddRange(newResolutions);
} }
public Resolutions visitBinaryExpr(ScopeStack scopes, Binary expr) private class ResolverBuilder : IExprVisitor<ScopeStack, Resolutions>
{ {
var left = Resolve(scopes, expr.Left); private class NameCollection
var right = Resolve(scopes, expr.Right);
return left.AddRange(right);
}
public Resolutions visitCallExpr(ScopeStack scopes, Call expr)
{
return (from arg in expr.Arguments
where arg is not null
select Resolve(scopes, arg))
.Aggregate(Resolve(scopes, expr.Left), AddRange);
}
public Resolutions visitGroupingExpr(ScopeStack scopes, Grouping expr)
{
return Resolve(scopes, expr.Expression);
}
public Resolutions visitIfExpr(ScopeStack scopes, If expr)
{
var condition = Resolve(scopes, expr.Condition);
var _then = Resolve(scopes, expr.Then);
var _else = Resolve(scopes, expr.Else);
return condition.AddRange(_then).AddRange(_else);
}
public Resolutions visitIndexerExpr(ScopeStack scopes, Indexer expr)
{
var left = Resolve(scopes, expr.Left);
var index = Resolve(scopes, expr.Index);
return left.AddRange(index);
}
public Resolutions visitLetExpr(ScopeStack scopes, Let expr)
{
var names = new NameCollection();
foreach (var binding in expr.Bindings)
{ {
switch (binding) public ImmutableHashSet<string> Names { get; private set; } = ImmutableHashSet<string>.Empty;
public void Add(Token name)
{ {
case FuncBinding fb: var value = (string)name.Literal!;
names.Add(fb.Name); if (Names.Contains(value))
break;
case VarBinding vb:
vb.Pattern.accept(names, PatternNameCollector.Instance);
break;
default:
throw new Exception("wtf there are no other binding types");
}
}
var newScopes = scopes.Push(names.Names);
// TODO Put the static constructivity check here.
// For now, just check that a definition only uses names that come before it.
// Note that this will preclude using shadowed variables in definitions.
return (from binding in expr.Bindings
select (binding switch
{ {
FuncBinding fb => ResolveFunction(newScopes, fb), Program.error(name.Start, $"Duplicate name declared in scope: {name.Lexeme}");
VarBinding vb => Resolve(newScopes, vb.Value), }
_ => throw new Exception("wtf there are no other binding types"), else
})) {
.Aggregate(Resolve(newScopes, expr.Body), AddRange); Names = Names.Add(value);
}
}
private Resolutions ResolveFunction(ScopeStack scopes, FuncBinding fb)
{
NameCollection names = new();
foreach (var param in fb.Params)
{
param.accept(names, PatternNameCollector.Instance);
}
return Resolve(scopes, names.Names, fb.Value);
}
public Resolutions visitListExpr(ScopeStack scopes, List expr)
{
return (from element in expr.Elements
select Resolve(scopes, element))
.Aggregate(EmptyResolutions, AddRange);
}
public Resolutions visitLiteralExpr(ScopeStack scopes, Literal expr)
{
return EmptyResolutions;
}
public Resolutions visitRecordExpr(ScopeStack scopes, Record expr)
{
var resolutions = (from extension in expr.Extensions
select Resolve(scopes, extension.Value))
.Aggregate(EmptyResolutions, AddRange);
if (expr.Base is not null)
{
resolutions = resolutions.AddRange(Resolve(scopes, expr.Base.Value));
resolutions = (from update in expr.Base.Updates
select Resolve(scopes, update.Value))
.Aggregate(resolutions, AddRange);
}
return resolutions;
}
public Resolutions visitSelectorExpr(ScopeStack scopes, Selector expr)
{
return Resolve(scopes, expr.Left);
}
public Resolutions visitSequenceExpr(ScopeStack scopes, Sequence expr)
{
return AddRange(Resolve(scopes, expr.Left), Resolve(scopes, expr.Right));
}
public Resolutions visitUnaryExpr(ScopeStack scopes, Unary expr)
{
return Resolve(scopes, expr.Right);
}
public Resolutions visitVariableExpr(ScopeStack scopes, Variable expr)
{
int depth = 0;
while (!scopes.IsEmpty)
{
scopes = scopes.Pop(out var scope);
if (scope.Contains((string)expr.Value.Literal!))
{
return EmptyResolutions.Add(expr, depth);
} }
depth++;
} }
// At this point, it must be a global. I don't like that we leave that resolution for runtime.
// TODO Don't leave that resolution for runtime.
// But I think that can wait until I make the resolver nameless.
return EmptyResolutions.Add(expr, depth);
}
public Resolutions visitVariantExpr(ScopeStack scopes, Variant expr) private class PatternNameCollector : IPatternVisitor<NameCollection, ValueTuple>
{
if (expr.Argument is not null)
{ {
return Resolve(scopes, expr.Argument); public readonly static PatternNameCollector Instance = new();
private static readonly ValueTuple Void = ValueTuple.Create();
private PatternNameCollector() { }
public ValueTuple visitFieldPatternPattern(NameCollection names, FieldPattern field)
{
if (field.Pattern is null)
{
names.Add(field.Name);
}
else
{
field.Pattern.accept(names, this);
}
return Void;
}
public ValueTuple visitRecordPatternPattern(NameCollection names, RecordPattern record)
{
record.Rest?.accept(names, this);
foreach (var field in record.Fields)
{
field.accept(names, this);
}
return Void;
}
public ValueTuple visitSimplePatternPattern(NameCollection names, SimplePattern simple)
{
if (simple.Identifier is not null)
{
names.Add(simple.Identifier);
}
return Void;
}
public ValueTuple visitVariantPatternPattern(NameCollection names, VariantPattern variant)
{
variant.Argument?.accept(names, this);
return Void;
}
} }
return EmptyResolutions;
}
public Resolutions visitWhenExpr(ScopeStack scopes, When expr) public static readonly ResolverBuilder Instance = new ResolverBuilder();
{
return (from _case in expr.Cases
select ResolveCase(_case))
.Aggregate(Resolve(scopes, expr.Head), AddRange);
Resolutions ResolveCase(VarBinding _case) private ResolverBuilder() { }
private static Resolutions AddRange(Resolutions r1, Resolutions r2) => r1.AddRange(r2);
public Resolutions Resolve(ScopeStack scopes, Expr expr)
{
return expr.accept(scopes, this);
}
private Resolutions Resolve(ScopeStack scopes, ImmutableHashSet<string> newScope, Expr value)
{
return Resolve(scopes.Push(newScope), value);
}
public Resolutions visitBinaryExpr(ScopeStack scopes, Binary expr)
{
var left = Resolve(scopes, expr.Left);
var right = Resolve(scopes, expr.Right);
return left.AddRange(right);
}
public Resolutions visitCallExpr(ScopeStack scopes, Call expr)
{
return (from arg in expr.Arguments
where arg is not null
select Resolve(scopes, arg))
.Aggregate(Resolve(scopes, expr.Left), AddRange);
}
public Resolutions visitGroupingExpr(ScopeStack scopes, Grouping expr)
{
return Resolve(scopes, expr.Expression);
}
public Resolutions visitIfExpr(ScopeStack scopes, If expr)
{
var condition = Resolve(scopes, expr.Condition);
var _then = Resolve(scopes, expr.Then);
var _else = Resolve(scopes, expr.Else);
return condition.AddRange(_then).AddRange(_else);
}
public Resolutions visitIndexerExpr(ScopeStack scopes, Indexer expr)
{
var left = Resolve(scopes, expr.Left);
var index = Resolve(scopes, expr.Index);
return left.AddRange(index);
}
public Resolutions visitLetExpr(ScopeStack scopes, Let expr)
{ {
var names = new NameCollection(); var names = new NameCollection();
_case.Pattern.accept(names, PatternNameCollector.Instance); foreach (var binding in expr.Bindings)
return Resolve(scopes, names.Names, _case.Value); {
switch (binding)
{
case FuncBinding fb:
names.Add(fb.Name);
break;
case VarBinding vb:
vb.Pattern.accept(names, PatternNameCollector.Instance);
break;
default:
throw new Exception("wtf there are no other binding types");
}
}
var newScopes = scopes.Push(names.Names);
// TODO Put the static constructivity check here.
// For now, just check that a definition only uses names that come before it.
// Note that this will preclude using shadowed variables in definitions.
return (from binding in expr.Bindings
select (binding switch
{
FuncBinding fb => ResolveFunction(newScopes, fb),
VarBinding vb => Resolve(newScopes, vb.Value),
_ => throw new Exception("wtf there are no other binding types"),
}))
.Aggregate(Resolve(newScopes, expr.Body), AddRange);
}
private Resolutions ResolveFunction(ScopeStack scopes, FuncBinding fb)
{
NameCollection names = new();
foreach (var param in fb.Params)
{
param.accept(names, PatternNameCollector.Instance);
}
return Resolve(scopes, names.Names, fb.Value);
}
public Resolutions visitListExpr(ScopeStack scopes, List expr)
{
return (from element in expr.Elements
select Resolve(scopes, element))
.Aggregate(EmptyResolutions, AddRange);
}
public Resolutions visitLiteralExpr(ScopeStack scopes, Literal expr)
{
return EmptyResolutions;
}
public Resolutions visitRecordExpr(ScopeStack scopes, Record expr)
{
var resolutions = (from extension in expr.Extensions
select Resolve(scopes, extension.Value))
.Aggregate(EmptyResolutions, AddRange);
if (expr.Base is not null)
{
resolutions = resolutions.AddRange(Resolve(scopes, expr.Base.Value));
resolutions = (from update in expr.Base.Updates
select Resolve(scopes, update.Value))
.Aggregate(resolutions, AddRange);
}
return resolutions;
}
public Resolutions visitSelectorExpr(ScopeStack scopes, Selector expr)
{
return Resolve(scopes, expr.Left);
}
public Resolutions visitSequenceExpr(ScopeStack scopes, Sequence expr)
{
return AddRange(Resolve(scopes, expr.Left), Resolve(scopes, expr.Right));
}
public Resolutions visitUnaryExpr(ScopeStack scopes, Unary expr)
{
return Resolve(scopes, expr.Right);
}
public Resolutions visitVariableExpr(ScopeStack scopes, Variable expr)
{
int depth = 0;
while (!scopes.IsEmpty)
{
scopes = scopes.Pop(out var scope);
if (scope.Contains((string)expr.Value.Literal!))
{
return EmptyResolutions.Add(expr, depth);
}
depth++;
}
// At this point, it must be a global. I don't like that we leave that resolution for runtime.
// TODO Don't leave that resolution for runtime.
// But I think that can wait until I make the resolver nameless.
return EmptyResolutions.Add(expr, depth);
}
public Resolutions visitVariantExpr(ScopeStack scopes, Variant expr)
{
if (expr.Argument is not null)
{
return Resolve(scopes, expr.Argument);
}
return EmptyResolutions;
}
public Resolutions visitWhenExpr(ScopeStack scopes, When expr)
{
return (from _case in expr.Cases
select ResolveCase(_case))
.Aggregate(Resolve(scopes, expr.Head), AddRange);
Resolutions ResolveCase(VarBinding _case)
{
var names = new NameCollection();
_case.Pattern.accept(names, PatternNameCollector.Instance);
return Resolve(scopes, names.Names, _case.Value);
}
} }
} }
} }

View File

@ -1,4 +1,3 @@
Wire up resolver to interpreter
Move let-expr resolvability check to Resolver Move let-expr resolvability check to Resolver
Remove dynamic type checks (checkXXX methods) from interpreter Remove dynamic type checks (checkXXX methods) from interpreter
Use numerical indices in resolutions Use numerical indices in resolutions