From 6f3c82cad1cc5ecf6b5b2818ed7a6395d0a726fa Mon Sep 17 00:00:00 2001 From: Brandon Dyck Date: Sat, 1 Jul 2023 00:12:02 -0600 Subject: [PATCH] Added identifiers and field selectors to grammar --- AST.cs | 3 +++ ASTPrinter.cs | 16 +++++++++++++ Expr.g.cs | 19 ++++++++++++++++ Parser.cs | 60 +++++++++++++++++++++++++++++++++++++++---------- ast_classes.fsx | 6 ++++- 5 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 AST.cs diff --git a/AST.cs b/AST.cs new file mode 100644 index 0000000..4cd1572 --- /dev/null +++ b/AST.cs @@ -0,0 +1,3 @@ +namespace Finn.AST; + +public record Name(string Value, bool Quoted); diff --git a/ASTPrinter.cs b/ASTPrinter.cs index 9017e36..fbcb93f 100644 --- a/ASTPrinter.cs +++ b/ASTPrinter.cs @@ -39,6 +39,17 @@ class ASTPrinter : IVisitor 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 { return parenthesize("seq", expr.Left, expr.Right); } + + public string visitSelectorExpr(Selector expr) + { + return parenthesize(".", expr.Left, new Identifier { Value = expr.FieldName }); + } } diff --git a/Expr.g.cs b/Expr.g.cs index d743ae9..5b7533f 100644 --- a/Expr.g.cs +++ b/Expr.g.cs @@ -16,6 +16,8 @@ public interface IVisitor { 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(IVisitor 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(IVisitor visitor) + { + return visitor.visitSelectorExpr(this); + } +} diff --git a/Parser.cs b/Parser.cs index d4e2022..480525b 100644 --- a/Parser.cs +++ b/Parser.cs @@ -73,7 +73,15 @@ class Parser private Token consume(TokenType type, string message) { - if (check(type)) return advance(); + 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(); diff --git a/ast_classes.fsx b/ast_classes.fsx index 5ecb21a..6945bb1 100644 --- a/ast_classes.fsx +++ b/ast_classes.fsx @@ -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}"