finn-lang/Resolver.cs

293 lines
6.8 KiB
C#
Raw Normal View History

2023-10-04 05:32:24 +00:00
using System;
using System.Collections.Immutable;
using Finn;
using Finn.AST;
class Resolver : IExprVisitor<ImmutableStack<ImmutableHashSet<string>>, ValueTuple>
2023-10-04 05:32:24 +00:00
{
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 ValueTuple Void = ValueTuple.Create();
2023-10-04 05:32:24 +00:00
private readonly Interpreter Interpreter;
Resolver(Interpreter interpreter)
{
Interpreter = interpreter;
}
private void Resolve(ImmutableStack<ImmutableHashSet<string>> scopes, Expr expr)
2023-10-04 05:32:24 +00:00
{
expr.accept(scopes, this);
2023-10-04 05:32:24 +00:00
}
private void Resolve(ImmutableStack<ImmutableHashSet<string>> scopes, ImmutableHashSet<string> newScope, Expr value)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes.Push(newScope), value);
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitBinaryExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Binary expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Left);
Resolve(scopes, expr.Right);
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitCallExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Call expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Left);
2023-10-04 05:32:24 +00:00
foreach (var arg in expr.Arguments)
{
if (arg is not null)
{
Resolve(scopes, arg);
2023-10-04 05:32:24 +00:00
}
}
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitGroupingExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Grouping expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Expression);
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitIfExpr(ImmutableStack<ImmutableHashSet<string>> scopes, If expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Condition);
Resolve(scopes, expr.Then);
Resolve(scopes, expr.Else);
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitIndexerExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Indexer expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Left);
Resolve(scopes, expr.Index);
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitLetExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Let expr)
2023-10-04 05:32:24 +00:00
{
var names = new NameCollection();
foreach (var binding in expr.Bindings)
{
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);
2023-10-04 05:32:24 +00:00
// 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.
foreach (var binding in expr.Bindings)
{
switch (binding)
{
case FuncBinding fb:
ResolveFunction(newScopes, fb);
2023-10-04 05:32:24 +00:00
break;
case VarBinding vb:
Resolve(newScopes, vb.Value);
2023-10-04 05:32:24 +00:00
break;
default:
throw new Exception("wtf there are no other binding types");
}
}
Resolve(newScopes, expr.Body);
2023-10-04 05:32:24 +00:00
return Void;
2023-10-04 05:32:24 +00:00
}
private void ResolveFunction(ImmutableStack<ImmutableHashSet<string>> scopes, FuncBinding fb)
2023-10-04 05:32:24 +00:00
{
NameCollection names = new();
foreach (var param in fb.Params)
{
param.accept(names, PatternNameCollector.Instance);
}
Resolve(scopes, names.Names, fb.Value);
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitListExpr(ImmutableStack<ImmutableHashSet<string>> scopes, List expr)
2023-10-04 05:32:24 +00:00
{
foreach (var element in expr.Elements)
{
Resolve(scopes, element);
2023-10-04 05:32:24 +00:00
}
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitLiteralExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Literal expr)
2023-10-04 05:32:24 +00:00
{
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitRecordExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Record expr)
2023-10-04 05:32:24 +00:00
{
if (expr.Base is not null)
{
Resolve(scopes, expr.Base.Value);
2023-10-04 05:32:24 +00:00
foreach (var update in expr.Base.Updates)
{
if (update.Value is not null)
{
Resolve(scopes, update.Value);
2023-10-04 05:32:24 +00:00
}
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);
2023-10-04 05:32:24 +00:00
}
else
{
// TODO Resolve extension.Name like a var expression.
}
}
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitSelectorExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Selector expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Left);
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitSequenceExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Sequence expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Left);
Resolve(scopes, expr.Right);
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitUnaryExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Unary expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Right);
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitVariableExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Variable expr)
2023-10-04 05:32:24 +00:00
{
ResolveLocal(scopes, expr, expr.Value);
return Void;
2023-10-04 05:32:24 +00:00
}
private void ResolveLocal(ImmutableStack<ImmutableHashSet<string>> scopes, Expr expr, Token name)
2023-10-04 05:32:24 +00:00
{
int depth = 0;
while (!scopes.IsEmpty)
{
scopes = scopes.Pop(out var scope);
if (scope.Contains((string)name.Literal!))
{
Interpreter.Resolve(expr, depth);
return;
}
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.
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitVariantExpr(ImmutableStack<ImmutableHashSet<string>> scopes, Variant expr)
2023-10-04 05:32:24 +00:00
{
if (expr.Argument is not null)
{
Resolve(scopes, expr.Argument);
2023-10-04 05:32:24 +00:00
}
return Void;
2023-10-04 05:32:24 +00:00
}
public ValueTuple visitWhenExpr(ImmutableStack<ImmutableHashSet<string>> scopes, When expr)
2023-10-04 05:32:24 +00:00
{
Resolve(scopes, expr.Head);
2023-10-04 05:32:24 +00:00
foreach (var _case in expr.Cases)
{
var names = new NameCollection();
_case.Pattern.accept(names, PatternNameCollector.Instance);
Resolve(scopes, names.Names, _case.Value);
2023-10-04 05:32:24 +00:00
}
return Void;
2023-10-04 05:32:24 +00:00
}
}