using System; using System.Collections.Generic; using System.Collections.Immutable; using Finn; using Finn.AST; class Resolver : IExprVisitor { private class NameCollection { public ImmutableHashSet Names { get; private set; } = ImmutableHashSet.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 { 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> scopes = new(); Resolver(Interpreter interpreter) { Interpreter = interpreter; } private void Resolve(ValueTuple context, Expr expr) { expr.accept(context, this); } private void ResolveInNewScope(ValueTuple context, IReadOnlySet vars, Expr expr) { BeginScope(vars); Resolve(context, expr); EndScope(); } private void BeginScope(IReadOnlySet 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; } }