Parse record literals

This commit is contained in:
Brandon Dyck 2023-07-02 11:38:48 -06:00
parent 5b0ca1ca6d
commit 63edbae650
6 changed files with 110 additions and 5 deletions

4
AST.cs
View File

@ -1,3 +1,7 @@
namespace Finn.AST; namespace Finn.AST;
public record Name(string Value, bool Quoted); public record Name(string Value, bool Quoted);
public record Field(Name Name, Expr? Value);
public record BaseRecord(Expr Value, Field[] Updates);

View File

@ -83,4 +83,46 @@ class ASTPrinter : IVisitor<string>
} }
return parenthesize("variant", new Identifier { Value = expr.Tag }, expr.Argument); 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(')');
}
}
}
} }

View File

@ -20,6 +20,7 @@ public interface IVisitor<T> {
T visitIdentifierExpr(Identifier expr); T visitIdentifierExpr(Identifier expr);
T visitListExpr(List expr); T visitListExpr(List expr);
T visitVariantExpr(Variant expr); T visitVariantExpr(Variant expr);
T visitRecordExpr(Record expr);
T visitSelectorExpr(Selector expr); T visitSelectorExpr(Selector expr);
} }
public class Sequence : Expr public class Sequence : Expr
@ -101,6 +102,15 @@ public class Variant : Expr
return visitor.visitVariantExpr(this); return visitor.visitVariantExpr(this);
} }
} }
public class Record : Expr
{
public required Field[] Extensions { get; init; }
public required BaseRecord? Base { get; init; }
public override T accept<T>(IVisitor<T> visitor)
{
return visitor.visitRecordExpr(this);
}
}
public class Selector : Expr public class Selector : Expr
{ {
public required Expr Left { get; init; } public required Expr Left { get; init; }

View File

@ -44,10 +44,10 @@ class Parser
return false; return false;
} }
private bool check(TokenType type) private bool check(params TokenType[] types)
{ {
if (isAtEnd()) return false; if (isAtEnd()) return false;
return peek().type == type; return Array.IndexOf(types, peek().type) >= 0;
} }
private Token advance() private Token advance()
@ -327,7 +327,48 @@ class Parser
private Expr? record() private Expr? record()
{ {
return null; if (!match(TokenType.LBrace))
// TODO record {
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<Field>();
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<Field> fields = new List<Field>();
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();
}
} }
} }

View File

@ -39,7 +39,11 @@ class Program
Parser parser = new Parser(tokens); Parser parser = new Parser(tokens);
Expr? expression = parser.parse(); Expr? expression = parser.parse();
if (hadError || expression == null) return; if (hadError || expression == null)
{
hadError = false;
return;
}
Console.WriteLine(new ASTPrinter().print(expression)); Console.WriteLine(new ASTPrinter().print(expression));
} }

View File

@ -28,6 +28,10 @@ let types =
Fields = [ { Type = "Expr[]"; Name = "Elements" } ] } Fields = [ { Type = "Expr[]"; Name = "Elements" } ] }
{ Name = "Variant" { Name = "Variant"
Fields = [ { Type = "Name"; Name = "Tag" }; { Type = "Expr?"; Name = "Argument" } ] } Fields = [ { Type = "Name"; Name = "Tag" }; { Type = "Expr?"; Name = "Argument" } ] }
{ Name = "Record"
Fields =
[ { Type = "Field[]"; Name = "Extensions" }
{ Type = "BaseRecord?"; Name = "Base" } ] }
{ Name = "Selector" { Name = "Selector"
Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Name"; Name = "FieldName" } ] } ] Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Name"; Name = "FieldName" } ] } ]