diff --git a/AST.cs b/AST.cs index 41efef7..65d701d 100644 --- a/AST.cs +++ b/AST.cs @@ -1,4 +1,5 @@ using System.CodeDom.Compiler; +using System.Collections.Generic; namespace Finn.AST; @@ -38,3 +39,25 @@ public partial record Literal return this.Value.ToString()!; } } + +public record Binding(Pattern Pattern, Expr Value); + +public partial record SimplePattern +{ + public override string ToString() + { + if (this.Identifier == null) + { + return "_"; + } + return this.Identifier.ToString(); + } +} + +public partial record Let +{ + public override string ToString() + { + return $"Let {{ Bindings = {string.Join(", ", (IEnumerable)(this.Bindings))}, Body = {this.Body} }}"; + } +} \ No newline at end of file diff --git a/Expr.g.cs b/Expr.g.cs index c14052d..82eabff 100644 --- a/Expr.g.cs +++ b/Expr.g.cs @@ -24,6 +24,7 @@ public interface IExprVisitor { T visitSelectorExpr(Selector expr); T visitIndexerExpr(Indexer expr); T visitCallExpr(Call expr); + T visitLetExpr(Let expr); } public partial record Sequence(Expr Left, Expr Right) : Expr() { @@ -116,3 +117,10 @@ public partial record Call(Expr Left, Expr?[] Arguments) : Expr() return visitor.visitCallExpr(this); } } +public partial record Let(Binding[] Bindings, Expr Body) : Expr() +{ + public override T accept(IExprVisitor visitor) + { + return visitor.visitLetExpr(this); + } +} diff --git a/Parser.cs b/Parser.cs index 20b4c84..4a25549 100644 --- a/Parser.cs +++ b/Parser.cs @@ -158,7 +158,83 @@ class Parser Expr right = unary(); return new Unary(op, right); } - return control(); + return let(); + } + + private RecordPattern? recordPattern() + { + if (!match(TokenType.LBrace)) + { + return null; + } + throw new NotImplementedException("TODO record pattern"); + } + + private VariantPattern? variantPattern() + { + if (!match(TokenType.Backtick)) + { + return null; + } + Name? tag = name(); + if (tag == null) + { + throw error(peek(), "Expect identifier as tag name."); + } + if (!match(TokenType.LParen)) + { + return new(tag, null); + } + Pattern argument = pattern(); + consume(TokenType.RParen, "Expect ')' after variant argument."); + return new(tag, argument); + } + + private Pattern pattern() + { + if (match(TokenType.Blank)) + { + return new SimplePattern(null); + } + + var identifier = name(); + if (identifier != null) + { + return new SimplePattern(identifier); + } + + Pattern? p = recordPattern(); + if (p != null) + { + return p; + } + if ((p = variantPattern()) != null) + { + return p; + } + throw error(peek(), "Expect pattern after 'let'."); + } + + private Expr let() + { + if (!match(TokenType.Let)) + { + return control(); + } + + List bindings = new List(); + Pattern p = pattern(); + consume(TokenType.Equal, "Expect '=' after pattern."); + bindings.Add(new(p, expression())); + while (match(TokenType.And)) + { + p = pattern(); + consume(TokenType.Equal, "Expect '=' after pattern."); + bindings.Add(new(p, expression())); + } + consume(TokenType.In, "Expect 'in' after let-bindings."); + Expr body = expression(); + return new Let(bindings.ToArray(), body); } private Expr control() @@ -322,7 +398,7 @@ class Parser Expr? argument = null; if (tag == null) { - throw error(peek(), "Expect identifier after backtick."); + throw error(peek(), "Expect identifier as tag name."); } if (match(TokenType.LParen)) { diff --git a/Pattern.g.cs b/Pattern.g.cs index 6c449fd..bde6150 100644 --- a/Pattern.g.cs +++ b/Pattern.g.cs @@ -11,16 +11,16 @@ public abstract record Pattern() { public abstract T accept(IPatternVisitor visitor); } public interface IPatternVisitor { - T visitIdentifierPatternPattern(IdentifierPattern pattern); + T visitSimplePatternPattern(SimplePattern pattern); T visitVariantPatternPattern(VariantPattern pattern); T visitFieldPatternPattern(FieldPattern pattern); T visitRecordPatternPattern(RecordPattern pattern); } -public partial record IdentifierPattern(Name? Identifier) : Pattern() +public partial record SimplePattern(Name? Identifier) : Pattern() { public override T accept(IPatternVisitor visitor) { - return visitor.visitIdentifierPatternPattern(this); + return visitor.visitSimplePatternPattern(this); } } public partial record VariantPattern(Name Tag, Pattern? Argument) : Pattern() @@ -37,7 +37,7 @@ public partial record FieldPattern(Name Name, Pattern? Pattern) : Pattern() return visitor.visitFieldPatternPattern(this); } } -public partial record RecordPattern(FieldPattern[] Fields, IdentifierPattern Rest) : Pattern() +public partial record RecordPattern(FieldPattern[] Fields, SimplePattern? Rest) : Pattern() { public override T accept(IPatternVisitor visitor) { diff --git a/ast_classes.fsx b/ast_classes.fsx index 750ed0d..2cc9396 100644 --- a/ast_classes.fsx +++ b/ast_classes.fsx @@ -38,10 +38,15 @@ let exprTypes = { Name = "Indexer" Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Expr"; Name = "Index" } ] } { Name = "Call" - Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Expr?[]"; Name = "Arguments" } ] } ] + Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Expr?[]"; Name = "Arguments" } ] } + { Name = "Let" + Fields = + [ { Type = "Binding[]" + Name = "Bindings" } + { Type = "Expr"; Name = "Body" } ] } ] let patternTypes = - [ { Name = "IdentifierPattern" + [ { Name = "SimplePattern" Fields = [ { Type = "Name?"; Name = "Identifier" } ] } { Name = "VariantPattern" Fields = [ { Type = "Name"; Name = "Tag" }; { Type = "Pattern?"; Name = "Argument" } ] } @@ -51,7 +56,7 @@ let patternTypes = Fields = [ { Type = "FieldPattern[]" Name = "Fields" } - { Type = "IdentifierPattern" + { Type = "SimplePattern?" Name = "Rest" } ] } ] let visitorMethod baseName t = $"visit{t.Name}{baseName}"