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;
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);
}
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 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<T>(IVisitor<T> visitor)
{
return visitor.visitRecordExpr(this);
}
}
public class Selector : Expr
{
public required Expr Left { get; init; }

View File

@ -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()
@ -326,8 +326,49 @@ class Parser
}
private Expr? record()
{
if (!match(TokenType.LBrace))
{
return null;
// TODO record
}
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);
Expr? expression = parser.parse();
if (hadError || expression == null) return;
if (hadError || expression == null)
{
hadError = false;
return;
}
Console.WriteLine(new ASTPrinter().print(expression));
}

View File

@ -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" } ] } ]