Added identifiers and field selectors to grammar

This commit is contained in:
Brandon Dyck 2023-07-01 00:12:02 -06:00
parent 9b2f0cb968
commit 6f3c82cad1
5 changed files with 91 additions and 13 deletions

3
AST.cs Normal file
View File

@ -0,0 +1,3 @@
namespace Finn.AST;
public record Name(string Value, bool Quoted);

View File

@ -39,6 +39,17 @@ class ASTPrinter : IVisitor<string>
return expr.Value.ToString() ?? "";
}
private string formatName(Name name)
{
if (name.Quoted) return $"@\"{name.Value}\"";
return name.Value;
}
public string visitIdentifierExpr(Identifier expr)
{
return formatName(expr.Value);
}
public string visitUnaryExpr(Unary expr)
{
return parenthesize(expr.Op.lexeme, expr.Right);
@ -53,4 +64,9 @@ class ASTPrinter : IVisitor<string>
{
return parenthesize("seq", expr.Left, expr.Right);
}
public string visitSelectorExpr(Selector expr)
{
return parenthesize(".", expr.Left, new Identifier { Value = expr.FieldName });
}
}

View File

@ -16,6 +16,8 @@ public interface IVisitor<T> {
T visitLiteralExpr(Literal expr);
T visitUnaryExpr(Unary expr);
T visitIfExpr(If expr);
T visitIdentifierExpr(Identifier expr);
T visitSelectorExpr(Selector expr);
}
public class Sequence : Expr
{
@ -71,3 +73,20 @@ public class If : Expr
return visitor.visitIfExpr(this);
}
}
public class Identifier : Expr
{
public required Name Value { get; init; }
public override T accept<T>(IVisitor<T> visitor)
{
return visitor.visitIdentifierExpr(this);
}
}
public class Selector : Expr
{
public required Expr Left { get; init; }
public required Name FieldName { get; init; }
public override T accept<T>(IVisitor<T> visitor)
{
return visitor.visitSelectorExpr(this);
}
}

View File

@ -72,8 +72,16 @@ class Parser
}
private Token consume(TokenType type, string message)
{
return consume(new TokenType[] { type }, message);
}
private Token consume(TokenType[] types, string message)
{
foreach (var type in types)
{
if (check(type)) return advance();
}
throw error(peek(), message);
}
@ -160,7 +168,7 @@ class Parser
Expr right = unary();
return new Unary { Op = op, Right = right };
}
return ifExpr();
return control();
}
private Expr control()
@ -182,28 +190,56 @@ class Parser
return primary();
}
private Expr ifExpr()
private Name? name()
{
if (match(TokenType.If))
if (match(TokenType.Identifier))
{
Expr condition = expression();
consume(TokenType.Then, "Expect 'then' after condition.");
Expr thenCase = expression();
consume(TokenType.Else, "Expect 'else' after 'then' case.");
Expr elseCase = expression();
return new If { Condition = condition, Then = thenCase, Else = elseCase };
return new(previous().lexeme, false);
}
return primary();
if (match(TokenType.At))
{
Token literal = consume(TokenType.String, "Expect string literal after '@'.");
return new((string)(literal.literal ?? ""), true);
}
return null;
}
private Expr primary()
{
Expr expr = operand();
if (match(TokenType.Period))
{
Name? ident = name();
if (ident == null)
{
throw error(advance(), "Expect identifier after dot.");
}
return new Selector { Left = expr, FieldName = ident };
}
if (match(TokenType.LBracket))
{
throw new NotImplementedException("TODO index");
}
if (match(TokenType.LParen))
{
throw new NotImplementedException("TODO apply");
}
return expr;
}
private Expr operand()
{
if (match(TokenType.Number, TokenType.String))
{
return new Literal { Value = previous().literal };
}
var ident = name();
if (ident != null)
{
return new Identifier { Value = ident };
}
if (match(TokenType.LParen))
{
Expr expr = expression();

View File

@ -21,7 +21,11 @@ let types =
Fields =
[ { Type = "Expr"; Name = "Condition" }
{ Type = "Expr"; Name = "Then" }
{ Type = "Expr"; Name = "Else" } ] } ]
{ Type = "Expr"; Name = "Else" } ] }
{ Name = "Identifier"
Fields = [ { Type = "Name"; Name = "Value" } ] }
{ Name = "Selector"
Fields = [ { Type = "Expr"; Name = "Left" }; { Type = "Name"; Name = "FieldName" } ] } ]
let visitorMethod baseName t = $"visit{t.Name}{baseName}"