Added identifiers and field selectors to grammar
This commit is contained in:
parent
9b2f0cb968
commit
6f3c82cad1
3
AST.cs
Normal file
3
AST.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Finn.AST;
|
||||
|
||||
public record Name(string Value, bool Quoted);
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
19
Expr.g.cs
19
Expr.g.cs
@ -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);
|
||||
}
|
||||
}
|
||||
|
58
Parser.cs
58
Parser.cs
@ -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();
|
||||
|
@ -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}"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user