From 63edbae650078b5dfb1096664936ed0c80409cb9 Mon Sep 17 00:00:00 2001 From: Brandon Dyck Date: Sun, 2 Jul 2023 11:38:48 -0600 Subject: [PATCH] Parse record literals --- AST.cs | 4 ++++ ASTPrinter.cs | 42 ++++++++++++++++++++++++++++++++++++++++++ Expr.g.cs | 10 ++++++++++ Parser.cs | 49 +++++++++++++++++++++++++++++++++++++++++++++---- Program.cs | 6 +++++- ast_classes.fsx | 4 ++++ 6 files changed, 110 insertions(+), 5 deletions(-) diff --git a/AST.cs b/AST.cs index 4cd1572..de46020 100644 --- a/AST.cs +++ b/AST.cs @@ -1,3 +1,7 @@ namespace Finn.AST; public record Name(string Value, bool Quoted); + +public record Field(Name Name, Expr? Value); + +public record BaseRecord(Expr Value, Field[] Updates); diff --git a/ASTPrinter.cs b/ASTPrinter.cs index 4ea2d81..51bb2c8 100644 --- a/ASTPrinter.cs +++ b/ASTPrinter.cs @@ -83,4 +83,46 @@ class ASTPrinter : IVisitor } return parenthesize("variant", new Identifier { Value = expr.Tag }, expr.Argument); } + + public string visitRecordExpr(Record expr) + { + StringWriter w = new StringWriter(); + w.Write("(record"); + + writeFields("extend", expr.Extensions); + + if (expr.Base != null) + { + w.Write(" (base "); + w.Write(print(expr.Base.Value)); + writeFields("update", expr.Base.Updates); + w.Write(')'); + } + w.Write(')'); + + return w.ToString(); + + void writeFields(string header, Field[] fields) + { + if (fields.Length != 0) + { + w.Write($" ({header}"); + foreach (var field in fields) + { + w.Write(' '); + var name = formatName(field.Name); + if (field.Value == null) + { + w.Write(name); + } + else + { + w.Write(parenthesize(name, field.Value)); + } + } + w.Write(')'); + } + + } + } } diff --git a/Expr.g.cs b/Expr.g.cs index 1a6aaa4..14845e1 100644 --- a/Expr.g.cs +++ b/Expr.g.cs @@ -20,6 +20,7 @@ public interface IVisitor { T visitIdentifierExpr(Identifier expr); T visitListExpr(List expr); T visitVariantExpr(Variant expr); + T visitRecordExpr(Record expr); T visitSelectorExpr(Selector expr); } public class Sequence : Expr @@ -101,6 +102,15 @@ public class Variant : Expr return visitor.visitVariantExpr(this); } } +public class Record : Expr +{ + public required Field[] Extensions { get; init; } + public required BaseRecord? Base { get; init; } + public override T accept(IVisitor visitor) + { + return visitor.visitRecordExpr(this); + } +} public class Selector : Expr { public required Expr Left { get; init; } diff --git a/Parser.cs b/Parser.cs index e16d746..d804ef9 100644 --- a/Parser.cs +++ b/Parser.cs @@ -44,10 +44,10 @@ class Parser return false; } - private bool check(TokenType type) + private bool check(params TokenType[] types) { if (isAtEnd()) return false; - return peek().type == type; + return Array.IndexOf(types, peek().type) >= 0; } private Token advance() @@ -327,7 +327,48 @@ class Parser private Expr? record() { - return null; - // TODO record + if (!match(TokenType.LBrace)) + { + return null; + } + + var extensions = parseFields(TokenType.RBrace, TokenType.Pipe); + BaseRecord? baseRecord = null; + + if (match(TokenType.Pipe)) + { + var baseExpr = expression(); + var updates = match(TokenType.With) ? parseFields(TokenType.RBrace) : Array.Empty(); + baseRecord = new(baseExpr, updates); + } + + consume(TokenType.RBrace, "Expect '}' at end of record literal."); + return new Record + { + Extensions = extensions, + Base = baseRecord, + }; + + Field[] parseFields(params TokenType[] endAt) + { + List fields = new List(); + + while (!check(endAt)) + { + var fieldName = name(); + if (fieldName == null) + { + throw error(peek(), "Expect identifier as field name."); + } + var value = match(TokenType.Equal) ? expression() : null; + fields.Add(new(fieldName, value)); + if (!match(TokenType.Comma)) + { + break; + } + } + + return fields.ToArray(); + } } } diff --git a/Program.cs b/Program.cs index b49856e..cb8be40 100644 --- a/Program.cs +++ b/Program.cs @@ -39,7 +39,11 @@ class Program Parser parser = new Parser(tokens); Expr? expression = parser.parse(); - if (hadError || expression == null) return; + if (hadError || expression == null) + { + hadError = false; + return; + } Console.WriteLine(new ASTPrinter().print(expression)); } diff --git a/ast_classes.fsx b/ast_classes.fsx index 83ba4ab..080de40 100644 --- a/ast_classes.fsx +++ b/ast_classes.fsx @@ -28,6 +28,10 @@ let types = Fields = [ { Type = "Expr[]"; Name = "Elements" } ] } { Name = "Variant" Fields = [ { Type = "Name"; Name = "Tag" }; { Type = "Expr?"; Name = "Argument" } ] } + { Name = "Record" + Fields = + [ { Type = "Field[]"; Name = "Extensions" } + { Type = "BaseRecord?"; Name = "Base" } ] } { Name = "Selector" Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Name"; Name = "FieldName" } ] } ]