Return a dictionary of resolutions from Resolver

This commit is contained in:
Brandon Dyck 2023-10-04 12:31:10 -06:00
parent ce7a755b07
commit 5168890ea0
4 changed files with 91 additions and 121 deletions

2
AST.cs
View File

@ -2,7 +2,7 @@ using System.Collections.Generic;
namespace Finn.AST; namespace Finn.AST;
public record Field(Token Name, Expr? Value); public record Field(Token Name, Expr Value);
public record BaseRecord(Expr Value, Field[] Updates); public record BaseRecord(Expr Value, Field[] Updates);

View File

@ -491,7 +491,8 @@ class Parser
while (!check(endAt)) while (!check(endAt))
{ {
var fieldName = consume("Expect identifier as field name.", TokenType.Identifier, TokenType.QuotedIdentifier); var fieldName = consume("Expect identifier as field name.", TokenType.Identifier, TokenType.QuotedIdentifier);
var value = match(TokenType.Equal) ? expression() : null; // Desugar punned field.
var value = match(TokenType.Equal) ? expression() : new Variable(fieldName);
fields.Add(new(fieldName, value)); fields.Add(new(fieldName, value));
if (!match(TokenType.Comma)) if (!match(TokenType.Comma))
{ {

View File

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
using Finn; using Finn;
using Finn.AST; using Finn.AST;
using ScopeStack = System.Collections.Immutable.ImmutableStack<System.Collections.Immutable.ImmutableHashSet<string>>;
using Resolutions = System.Collections.Immutable.ImmutableDictionary<Finn.AST.Expr, int>;
using System.Collections.Generic;
class Resolver : IExprVisitor<ScopeStack, Resolutions>
class Resolver : IExprVisitor<ImmutableStack<ImmutableHashSet<string>>, ValueTuple>
{ {
private class NameCollection private class NameCollection
{ {
@ -71,66 +74,59 @@ class Resolver : IExprVisitor<ImmutableStack<ImmutableHashSet<string>>, ValueTup
} }
} }
private static readonly ValueTuple Void = ValueTuple.Create(); private static readonly Resolutions EmptyResolutions = Resolutions.Empty.WithComparers(ReferenceEqualityComparer.Instance);
private readonly Interpreter Interpreter; public static readonly Resolver Instance = new Resolver();
Resolver(Interpreter interpreter) private Resolver() { }
private static Resolutions AddRange(Resolutions r1, Resolutions r2) => r1.AddRange(r2);
private Resolutions Resolve(ScopeStack scopes, Expr expr)
{ {
Interpreter = interpreter; return expr.accept(scopes, this);
} }
private void Resolve(ImmutableStack<ImmutableHashSet<string>> scopes, Expr expr) private Resolutions Resolve(ScopeStack scopes, ImmutableHashSet<string> newScope, Expr value)
{ {
expr.accept(scopes, this); return Resolve(scopes.Push(newScope), value);
} }
private void Resolve(ImmutableStack<ImmutableHashSet<string>> scopes, ImmutableHashSet<string> newScope, Expr value) public Resolutions visitBinaryExpr(ScopeStack scopes, Binary expr)
{ {
Resolve(scopes.Push(newScope), value); var left = Resolve(scopes, expr.Left);
var right = Resolve(scopes, expr.Right);
return left.AddRange(right);
} }
public ValueTuple visitBinaryExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Binary expr) public Resolutions visitCallExpr(ScopeStack scopes, Call expr)
{ {
Resolve(scopes, expr.Left); return (from arg in expr.Arguments
Resolve(scopes, expr.Right); where arg is not null
return Void; select Resolve(scopes, arg))
.Aggregate(Resolve(scopes, expr.Left), AddRange);
} }
public ValueTuple visitCallExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Call expr) public Resolutions visitGroupingExpr(ScopeStack scopes, Grouping expr)
{ {
Resolve(scopes, expr.Left); return Resolve(scopes, expr.Expression);
foreach (var arg in expr.Arguments)
{
if (arg is not null)
{
Resolve(scopes, arg);
}
}
return Void;
} }
public ValueTuple visitGroupingExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Grouping expr) public Resolutions visitIfExpr(ScopeStack scopes, If expr)
{ {
Resolve(scopes, expr.Expression); var condition = Resolve(scopes, expr.Condition);
return Void; var _then = Resolve(scopes, expr.Then);
var _else = Resolve(scopes, expr.Else);
return condition.AddRange(_then).AddRange(_else);
} }
public ValueTuple visitIfExpr(ImmutableStack<ImmutableHashSet<string>> scopes, If expr) public Resolutions visitIndexerExpr(ScopeStack scopes, Indexer expr)
{ {
Resolve(scopes, expr.Condition); var left = Resolve(scopes, expr.Left);
Resolve(scopes, expr.Then); var index = Resolve(scopes, expr.Index);
Resolve(scopes, expr.Else); return left.AddRange(index);
return Void;
} }
public ValueTuple visitIndexerExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Indexer expr) public Resolutions visitLetExpr(ScopeStack scopes, Let expr)
{
Resolve(scopes, expr.Left);
Resolve(scopes, expr.Index);
return Void;
}
public ValueTuple visitLetExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Let expr)
{ {
var names = new NameCollection(); var names = new NameCollection();
foreach (var binding in expr.Bindings) foreach (var binding in expr.Bindings)
@ -154,140 +150,108 @@ class Resolver : IExprVisitor<ImmutableStack<ImmutableHashSet<string>>, ValueTup
// For now, just check that a definition only uses names that come before it. // For now, just check that a definition only uses names that come before it.
// Note that this will preclude using shadowed variables in definitions. // Note that this will preclude using shadowed variables in definitions.
foreach (var binding in expr.Bindings) return (from binding in expr.Bindings
select (binding switch
{ {
switch (binding) FuncBinding fb => ResolveFunction(newScopes, fb),
{ VarBinding vb => Resolve(newScopes, vb.Value),
case FuncBinding fb: _ => throw new Exception("wtf there are no other binding types"),
ResolveFunction(newScopes, fb); }))
break; .Aggregate(Resolve(newScopes, expr.Body), AddRange);
case VarBinding vb:
Resolve(newScopes, vb.Value);
break;
default:
throw new Exception("wtf there are no other binding types");
}
}
Resolve(newScopes, expr.Body);
return Void;
} }
private void ResolveFunction(ImmutableStack<ImmutableHashSet<string>> scopes, FuncBinding fb) private Resolutions ResolveFunction(ScopeStack scopes, FuncBinding fb)
{ {
NameCollection names = new(); NameCollection names = new();
foreach (var param in fb.Params) foreach (var param in fb.Params)
{ {
param.accept(names, PatternNameCollector.Instance); param.accept(names, PatternNameCollector.Instance);
} }
Resolve(scopes, names.Names, fb.Value); return Resolve(scopes, names.Names, fb.Value);
} }
public ValueTuple visitListExpr(ImmutableStack<ImmutableHashSet<string>> scopes, List expr) public Resolutions visitListExpr(ScopeStack scopes, List expr)
{ {
foreach (var element in expr.Elements) return (from element in expr.Elements
{ select Resolve(scopes, element))
Resolve(scopes, element); .Aggregate(EmptyResolutions, AddRange);
}
return Void;
} }
public ValueTuple visitLiteralExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Literal expr) public Resolutions visitLiteralExpr(ScopeStack scopes, Literal expr)
{ {
return Void; return EmptyResolutions;
} }
public ValueTuple visitRecordExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Record expr) 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) if (expr.Base is not null)
{ {
Resolve(scopes, expr.Base.Value); resolutions = resolutions.AddRange(Resolve(scopes, expr.Base.Value));
foreach (var update in expr.Base.Updates) resolutions = (from update in expr.Base.Updates
{ select Resolve(scopes, update.Value))
if (update.Value is not null) .Aggregate(resolutions, AddRange);
{
Resolve(scopes, update.Value);
}
else
{
// TODO Resolve update.Name like a var expression.
}
}
}
foreach (var extension in expr.Extensions)
{
if (extension.Value is not null)
{
Resolve(scopes, extension.Value);
}
else
{
// TODO Resolve extension.Name like a var expression.
}
}
return Void;
} }
public ValueTuple visitSelectorExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Selector expr) return resolutions;
{
Resolve(scopes, expr.Left);
return Void;
} }
public ValueTuple visitSequenceExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Sequence expr) public Resolutions visitSelectorExpr(ScopeStack scopes, Selector expr)
{ {
Resolve(scopes, expr.Left); return Resolve(scopes, expr.Left);
Resolve(scopes, expr.Right);
return Void;
} }
public ValueTuple visitUnaryExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Unary expr) public Resolutions visitSequenceExpr(ScopeStack scopes, Sequence expr)
{ {
Resolve(scopes, expr.Right); return AddRange(Resolve(scopes, expr.Left), Resolve(scopes, expr.Right));
return Void;
} }
public ValueTuple visitVariableExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Variable expr) public Resolutions visitUnaryExpr(ScopeStack scopes, Unary expr)
{ {
ResolveLocal(scopes, expr, expr.Value); return Resolve(scopes, expr.Right);
return Void;
} }
private void ResolveLocal(ImmutableStack<ImmutableHashSet<string>> scopes, Expr expr, Token name) public Resolutions visitVariableExpr(ScopeStack scopes, Variable expr)
{ {
int depth = 0; int depth = 0;
while (!scopes.IsEmpty) while (!scopes.IsEmpty)
{ {
scopes = scopes.Pop(out var scope); scopes = scopes.Pop(out var scope);
if (scope.Contains((string)name.Literal!)) if (scope.Contains((string)expr.Value.Literal!))
{ {
Interpreter.Resolve(expr, depth); return EmptyResolutions.Add(expr, depth);
return;
} }
depth++; depth++;
} }
// At this point, it must be a global. I don't like that we leave that resolution for runtime. // 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. // 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 ValueTuple visitVariantExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Variant expr) public Resolutions visitVariantExpr(ScopeStack scopes, Variant expr)
{ {
if (expr.Argument is not null) if (expr.Argument is not null)
{ {
Resolve(scopes, expr.Argument); return Resolve(scopes, expr.Argument);
} }
return Void; return EmptyResolutions;
} }
public ValueTuple visitWhenExpr(ImmutableStack<ImmutableHashSet<string>> scopes, When expr) public Resolutions visitWhenExpr(ScopeStack scopes, When expr)
{ {
Resolve(scopes, expr.Head); return (from _case in expr.Cases
foreach (var _case in expr.Cases) select ResolveCase(_case))
.Aggregate(Resolve(scopes, expr.Head), AddRange);
Resolutions ResolveCase(VarBinding _case)
{ {
var names = new NameCollection(); var names = new NameCollection();
_case.Pattern.accept(names, PatternNameCollector.Instance); _case.Pattern.accept(names, PatternNameCollector.Instance);
Resolve(scopes, names.Names, _case.Value); return Resolve(scopes, names.Names, _case.Value);
} }
return Void;
} }
} }

View File

@ -1,3 +1,8 @@
Wire up resolver to interpreter
Move let-expr resolvability check to Resolver
Remove dynamic type checks (checkXXX methods) from interpreter
Use numerical indices in resolutions
Statically resolve global references
Capitalize stuff to match common C# style Capitalize stuff to match common C# style
Replace `with` keyword with `but` Replace `with` keyword with `but`
Figure out multiple-binding let-expr semantics Figure out multiple-binding let-expr semantics