diff --git a/AST.cs b/AST.cs index 1b2e2bb..af1f4c0 100644 --- a/AST.cs +++ b/AST.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; namespace Finn.AST; -public record Field(Token Name, Expr? Value); +public record Field(Token Name, Expr Value); public record BaseRecord(Expr Value, Field[] Updates); diff --git a/Parser.cs b/Parser.cs index 359484b..1262d03 100644 --- a/Parser.cs +++ b/Parser.cs @@ -491,7 +491,8 @@ class Parser while (!check(endAt)) { 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)); if (!match(TokenType.Comma)) { diff --git a/Resolver.cs b/Resolver.cs index d090fa0..a8924c7 100644 --- a/Resolver.cs +++ b/Resolver.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Immutable; +using System.Linq; using Finn; using Finn.AST; +using ScopeStack = System.Collections.Immutable.ImmutableStack>; +using Resolutions = System.Collections.Immutable.ImmutableDictionary; +using System.Collections.Generic; - -class Resolver : IExprVisitor>, ValueTuple> +class Resolver : IExprVisitor { private class NameCollection { @@ -71,66 +74,59 @@ class Resolver : IExprVisitor>, ValueTup } } - private static readonly ValueTuple Void = ValueTuple.Create(); - private readonly Interpreter Interpreter; + private static readonly Resolutions EmptyResolutions = Resolutions.Empty.WithComparers(ReferenceEqualityComparer.Instance); + 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> scopes, Expr expr) + private Resolutions Resolve(ScopeStack scopes, ImmutableHashSet newScope, Expr value) { - expr.accept(scopes, this); + return Resolve(scopes.Push(newScope), value); } - private void Resolve(ImmutableStack> scopes, ImmutableHashSet 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> scopes, Binary expr) + public Resolutions visitCallExpr(ScopeStack scopes, Call expr) { - Resolve(scopes, expr.Left); - Resolve(scopes, expr.Right); - return Void; + return (from arg in expr.Arguments + where arg is not null + select Resolve(scopes, arg)) + .Aggregate(Resolve(scopes, expr.Left), AddRange); } - public ValueTuple visitCallExpr(ImmutableStack> scopes, Call expr) + public Resolutions visitGroupingExpr(ScopeStack scopes, Grouping expr) { - Resolve(scopes, expr.Left); - foreach (var arg in expr.Arguments) - { - if (arg is not null) - { - Resolve(scopes, arg); - } - } - return Void; + return Resolve(scopes, expr.Expression); } - public ValueTuple visitGroupingExpr(ImmutableStack> scopes, Grouping expr) + public Resolutions visitIfExpr(ScopeStack scopes, If expr) { - Resolve(scopes, expr.Expression); - return Void; + 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 ValueTuple visitIfExpr(ImmutableStack> scopes, If expr) + public Resolutions visitIndexerExpr(ScopeStack scopes, Indexer expr) { - Resolve(scopes, expr.Condition); - Resolve(scopes, expr.Then); - Resolve(scopes, expr.Else); - return Void; + var left = Resolve(scopes, expr.Left); + var index = Resolve(scopes, expr.Index); + return left.AddRange(index); } - public ValueTuple visitIndexerExpr(ImmutableStack> scopes, Indexer expr) - { - Resolve(scopes, expr.Left); - Resolve(scopes, expr.Index); - return Void; - } - - public ValueTuple visitLetExpr(ImmutableStack> scopes, Let expr) + public Resolutions visitLetExpr(ScopeStack scopes, Let expr) { var names = new NameCollection(); foreach (var binding in expr.Bindings) @@ -154,140 +150,108 @@ class Resolver : IExprVisitor>, ValueTup // 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); - break; - case VarBinding vb: - Resolve(newScopes, vb.Value); - break; - default: - throw new Exception("wtf there are no other binding types"); - } - } - Resolve(newScopes, expr.Body); + 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); - return Void; } - private void ResolveFunction(ImmutableStack> scopes, FuncBinding fb) + private Resolutions ResolveFunction(ScopeStack scopes, FuncBinding fb) { NameCollection names = new(); foreach (var param in fb.Params) { param.accept(names, PatternNameCollector.Instance); } - Resolve(scopes, names.Names, fb.Value); + return Resolve(scopes, names.Names, fb.Value); } - public ValueTuple visitListExpr(ImmutableStack> scopes, List expr) + public Resolutions visitListExpr(ScopeStack scopes, List expr) { - foreach (var element in expr.Elements) - { - Resolve(scopes, element); - } - return Void; + return (from element in expr.Elements + select Resolve(scopes, element)) + .Aggregate(EmptyResolutions, AddRange); } - public ValueTuple visitLiteralExpr(ImmutableStack> scopes, Literal expr) + public Resolutions visitLiteralExpr(ScopeStack scopes, Literal expr) { - return Void; + return EmptyResolutions; } - public ValueTuple visitRecordExpr(ImmutableStack> 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) { - Resolve(scopes, expr.Base.Value); - foreach (var update in expr.Base.Updates) - { - if (update.Value is not null) - { - Resolve(scopes, update.Value); - } - else - { - // TODO Resolve update.Name like a var expression. - } - } + resolutions = resolutions.AddRange(Resolve(scopes, expr.Base.Value)); + resolutions = (from update in expr.Base.Updates + select Resolve(scopes, update.Value)) + .Aggregate(resolutions, AddRange); } - 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; + + return resolutions; } - public ValueTuple visitSelectorExpr(ImmutableStack> scopes, Selector expr) + public Resolutions visitSelectorExpr(ScopeStack scopes, Selector expr) { - Resolve(scopes, expr.Left); - return Void; + return Resolve(scopes, expr.Left); } - public ValueTuple visitSequenceExpr(ImmutableStack> scopes, Sequence expr) + public Resolutions visitSequenceExpr(ScopeStack scopes, Sequence expr) { - Resolve(scopes, expr.Left); - Resolve(scopes, expr.Right); - return Void; + return AddRange(Resolve(scopes, expr.Left), Resolve(scopes, expr.Right)); } - public ValueTuple visitUnaryExpr(ImmutableStack> scopes, Unary expr) + public Resolutions visitUnaryExpr(ScopeStack scopes, Unary expr) { - Resolve(scopes, expr.Right); - return Void; + return Resolve(scopes, expr.Right); } - public ValueTuple visitVariableExpr(ImmutableStack> scopes, Variable expr) - { - ResolveLocal(scopes, expr, expr.Value); - return Void; - } - - private void ResolveLocal(ImmutableStack> scopes, Expr expr, Token name) + public Resolutions visitVariableExpr(ScopeStack scopes, Variable expr) { int depth = 0; while (!scopes.IsEmpty) { scopes = scopes.Pop(out var scope); - if (scope.Contains((string)name.Literal!)) + if (scope.Contains((string)expr.Value.Literal!)) { - Interpreter.Resolve(expr, depth); - return; + 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 ValueTuple visitVariantExpr(ImmutableStack> scopes, Variant expr) + public Resolutions visitVariantExpr(ScopeStack scopes, Variant expr) { if (expr.Argument is not null) { - Resolve(scopes, expr.Argument); + return Resolve(scopes, expr.Argument); } - return Void; + return EmptyResolutions; } - public ValueTuple visitWhenExpr(ImmutableStack> scopes, When expr) + public Resolutions visitWhenExpr(ScopeStack scopes, When expr) { - Resolve(scopes, expr.Head); - foreach (var _case in expr.Cases) + 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); - Resolve(scopes, names.Names, _case.Value); + return Resolve(scopes, names.Names, _case.Value); } - return Void; } } \ No newline at end of file diff --git a/TODO.txt b/TODO.txt index 3b9c649..83ddf0c 100644 --- a/TODO.txt +++ b/TODO.txt @@ -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 Replace `with` keyword with `but` Figure out multiple-binding let-expr semantics