Added half-baked resolver
This commit is contained in:
parent
bc57ed9df0
commit
d5ffd1e414
297
Resolver.cs
Normal file
297
Resolver.cs
Normal file
@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Finn;
|
||||
using Finn.AST;
|
||||
|
||||
|
||||
|
||||
class Resolver : IExprVisitor<ValueTuple, ValueTuple>
|
||||
{
|
||||
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 readonly Interpreter Interpreter;
|
||||
private readonly Stack<IReadOnlySet<string>> scopes = new();
|
||||
|
||||
Resolver(Interpreter interpreter)
|
||||
{
|
||||
Interpreter = interpreter;
|
||||
}
|
||||
|
||||
private void Resolve(ValueTuple context, Expr expr)
|
||||
{
|
||||
expr.accept(context, this);
|
||||
}
|
||||
|
||||
private void ResolveInNewScope(ValueTuple context, IReadOnlySet<string> vars, Expr expr)
|
||||
{
|
||||
BeginScope(vars);
|
||||
Resolve(context, expr);
|
||||
EndScope();
|
||||
}
|
||||
|
||||
private void BeginScope(IReadOnlySet<string> vars)
|
||||
{
|
||||
scopes.Push(vars);
|
||||
}
|
||||
|
||||
private void EndScope()
|
||||
{
|
||||
scopes.Pop();
|
||||
}
|
||||
|
||||
public ValueTuple visitBinaryExpr(ValueTuple context, Binary expr)
|
||||
{
|
||||
Resolve(context, expr.Left);
|
||||
Resolve(context, expr.Right);
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitCallExpr(ValueTuple context, Call expr)
|
||||
{
|
||||
Resolve(context, expr.Left);
|
||||
foreach (var arg in expr.Arguments)
|
||||
{
|
||||
if (arg is not null)
|
||||
{
|
||||
Resolve(context, arg);
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitGroupingExpr(ValueTuple context, Grouping expr)
|
||||
{
|
||||
Resolve(context, expr.Expression);
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitIfExpr(ValueTuple context, If expr)
|
||||
{
|
||||
Resolve(context, expr.Condition);
|
||||
Resolve(context, expr.Then);
|
||||
Resolve(context, expr.Else);
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitIndexerExpr(ValueTuple context, Indexer expr)
|
||||
{
|
||||
Resolve(context, expr.Left);
|
||||
Resolve(context, expr.Index);
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitLetExpr(ValueTuple context, Let expr)
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
BeginScope(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.
|
||||
|
||||
foreach (var binding in expr.Bindings)
|
||||
{
|
||||
switch (binding)
|
||||
{
|
||||
case FuncBinding fb:
|
||||
ResolveFunction(context, fb);
|
||||
break;
|
||||
case VarBinding vb:
|
||||
Resolve(context, vb.Value);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("wtf there are no other binding types");
|
||||
}
|
||||
}
|
||||
Resolve(context, expr.Body);
|
||||
|
||||
EndScope();
|
||||
return context;
|
||||
}
|
||||
|
||||
private void ResolveFunction(ValueTuple context, FuncBinding fb)
|
||||
{
|
||||
NameCollection names = new();
|
||||
foreach (var param in fb.Params)
|
||||
{
|
||||
param.accept(names, PatternNameCollector.Instance);
|
||||
}
|
||||
ResolveInNewScope(context, names.Names, fb.Value);
|
||||
}
|
||||
|
||||
public ValueTuple visitListExpr(ValueTuple context, List expr)
|
||||
{
|
||||
foreach (var element in expr.Elements)
|
||||
{
|
||||
Resolve(context, element);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitLiteralExpr(ValueTuple context, Literal expr)
|
||||
{
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitRecordExpr(ValueTuple context, Record expr)
|
||||
{
|
||||
if (expr.Base is not null)
|
||||
{
|
||||
Resolve(context, expr.Base.Value);
|
||||
foreach (var update in expr.Base.Updates)
|
||||
{
|
||||
if (update.Value is not null)
|
||||
{
|
||||
Resolve(context, update.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO Resolve update.Name like a var expression.
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var extension in expr.Extensions)
|
||||
{
|
||||
if (extension.Value is not null)
|
||||
{
|
||||
Resolve(context, extension.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO Resolve extension.Name like a var expression.
|
||||
}
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitSelectorExpr(ValueTuple context, Selector expr)
|
||||
{
|
||||
Resolve(context, expr.Left);
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitSequenceExpr(ValueTuple context, Sequence expr)
|
||||
{
|
||||
Resolve(context, expr.Left);
|
||||
Resolve(context, expr.Right);
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitUnaryExpr(ValueTuple context, Unary expr)
|
||||
{
|
||||
Resolve(context, expr.Right);
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitVariableExpr(ValueTuple context, Variable expr)
|
||||
{
|
||||
ResolveLocal(expr, expr.Value);
|
||||
return context;
|
||||
}
|
||||
|
||||
private void ResolveLocal(Expr expr, Token name)
|
||||
{
|
||||
// TODO I can't peek past the top element of the stack (as is correct for a stack).
|
||||
// Switch to an immutable stack so I can just pop stuff off, or switch to a list.
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ValueTuple visitVariantExpr(ValueTuple context, Variant expr)
|
||||
{
|
||||
if (expr.Argument is not null)
|
||||
{
|
||||
Resolve(context, expr.Argument);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public ValueTuple visitWhenExpr(ValueTuple context, When expr)
|
||||
{
|
||||
Resolve(context, expr.Head);
|
||||
foreach (var _case in expr.Cases)
|
||||
{
|
||||
var names = new NameCollection();
|
||||
_case.Pattern.accept(names, PatternNameCollector.Instance);
|
||||
ResolveInNewScope(context, names.Names, _case.Value);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
5
TODO.txt
5
TODO.txt
@ -1,8 +1,9 @@
|
||||
Allow variable defs to reference earlier ones in same binding list
|
||||
Capitalize stuff to match common C# style
|
||||
Replace `with` keyword with `but`
|
||||
Figure out multiple-binding let-expr semantics
|
||||
Clean up visibility
|
||||
Inject error handling into parser and scanner
|
||||
Think about a way to hide record fields
|
||||
- Maybe a compile-time primitive for generating globally unique symbols?
|
||||
Would also need a syntax to use those symbols.
|
||||
- Use @ as an operator to convert a comptime-known string to an identifier
|
||||
Replace `with` keyword with `but`
|
Loading…
Reference in New Issue
Block a user