Parse a subset of expressions
This commit is contained in:
parent
b9b8c8e24b
commit
77b1fab74f
@ -6,7 +6,7 @@ namespace Finn;
|
||||
|
||||
class ASTPrinter : IVisitor<string>
|
||||
{
|
||||
string print(Expr expr)
|
||||
public string print(Expr expr)
|
||||
{
|
||||
return expr.accept(this);
|
||||
}
|
||||
|
173
Parser.cs
Normal file
173
Parser.cs
Normal file
@ -0,0 +1,173 @@
|
||||
namespace Finn;
|
||||
|
||||
using Finn.AST;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
class Parser
|
||||
{
|
||||
private class ParseError : Exception { }
|
||||
|
||||
private readonly List<Token> tokens;
|
||||
private int current = 0;
|
||||
|
||||
public Parser(List<Token> tokens)
|
||||
{
|
||||
this.tokens = tokens;
|
||||
}
|
||||
|
||||
public Expr? parse()
|
||||
{
|
||||
try
|
||||
{
|
||||
return expression();
|
||||
}
|
||||
catch (ParseError)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Parsing primitives
|
||||
|
||||
private bool match(params TokenType[] types)
|
||||
{
|
||||
foreach (TokenType type in types)
|
||||
{
|
||||
if (check(type))
|
||||
{
|
||||
advance();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool check(TokenType type)
|
||||
{
|
||||
if (isAtEnd()) return false;
|
||||
return peek().type == type;
|
||||
}
|
||||
|
||||
private Token advance()
|
||||
{
|
||||
if (!isAtEnd()) current++;
|
||||
return previous();
|
||||
}
|
||||
|
||||
private bool isAtEnd()
|
||||
{
|
||||
return peek().type == TokenType.EOF;
|
||||
}
|
||||
|
||||
private Token peek()
|
||||
{
|
||||
return tokens[current];
|
||||
}
|
||||
|
||||
private Token previous()
|
||||
{
|
||||
return tokens[current - 1];
|
||||
}
|
||||
|
||||
private Token consume(TokenType type, string message)
|
||||
{
|
||||
if (check(type)) return advance();
|
||||
|
||||
throw error(peek(), message);
|
||||
}
|
||||
|
||||
private ParseError error(Token token, string message)
|
||||
{
|
||||
Program.error(token, message);
|
||||
return new ParseError();
|
||||
}
|
||||
|
||||
private void synchronize()
|
||||
{
|
||||
advance();
|
||||
|
||||
while (!isAtEnd())
|
||||
{
|
||||
switch (peek().type)
|
||||
{
|
||||
case TokenType.Def:
|
||||
case TokenType.Type:
|
||||
return;
|
||||
}
|
||||
|
||||
advance();
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
private Expr binaryLeft(Func<Expr> next, params TokenType[] types)
|
||||
{
|
||||
Expr expr = next();
|
||||
|
||||
while (match(types))
|
||||
{
|
||||
Token op = previous();
|
||||
Expr right = next();
|
||||
expr = new Binary { Left = expr, Op = op, Right = right };
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
// Rules
|
||||
|
||||
private Expr expression()
|
||||
{
|
||||
return equality();
|
||||
}
|
||||
|
||||
private Expr equality()
|
||||
{
|
||||
Expr expr = comparison();
|
||||
|
||||
while (match(TokenType.BangEqual, TokenType.DoubleEqual))
|
||||
{
|
||||
Token op = previous();
|
||||
Expr right = comparison();
|
||||
expr = new Binary { Left = expr, Op = op, Right = right };
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
private Expr comparison() =>
|
||||
binaryLeft(sum, TokenType.Greater, TokenType.GreaterEqual, TokenType.Less, TokenType.LessEqual);
|
||||
|
||||
private Expr sum() => binaryLeft(product, TokenType.Minus, TokenType.Plus);
|
||||
|
||||
private Expr product() => binaryLeft(unary, TokenType.Slash, TokenType.Asterisk);
|
||||
private Expr unary()
|
||||
{
|
||||
if (match(TokenType.Bang, TokenType.Minus))
|
||||
{
|
||||
Token op = previous();
|
||||
Expr right = unary();
|
||||
return new Unary { Op = op, Right = right };
|
||||
}
|
||||
return primary();
|
||||
}
|
||||
|
||||
private Expr primary()
|
||||
{
|
||||
if (match(TokenType.Number, TokenType.String))
|
||||
{
|
||||
return new Literal { Value = previous().literal };
|
||||
}
|
||||
|
||||
if (match(TokenType.LParen))
|
||||
{
|
||||
Expr expr = expression();
|
||||
consume(TokenType.RParen, "Expect ')' after expression.");
|
||||
return new Grouping { Expression = expr };
|
||||
}
|
||||
|
||||
throw error(peek(), "Expect expression.");
|
||||
}
|
||||
}
|
25
Program.cs
25
Program.cs
@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Finn.AST;
|
||||
|
||||
namespace Finn;
|
||||
|
||||
@ -36,11 +36,12 @@ class Program
|
||||
{
|
||||
var scanner = new Scanner(src);
|
||||
List<Token> tokens = scanner.scanTokens();
|
||||
Parser parser = new Parser(tokens);
|
||||
Expr? expression = parser.parse();
|
||||
|
||||
foreach (Token token in tokens)
|
||||
{
|
||||
Console.WriteLine(token);
|
||||
}
|
||||
if (hadError || expression == null) return;
|
||||
|
||||
Console.WriteLine(new ASTPrinter().print(expression));
|
||||
}
|
||||
|
||||
static void runPrompt()
|
||||
@ -62,6 +63,18 @@ class Program
|
||||
report(line, "", message);
|
||||
}
|
||||
|
||||
public static void error(Token token, String message)
|
||||
{
|
||||
if (token.type == TokenType.EOF)
|
||||
{
|
||||
report(token.line, " at end", message);
|
||||
}
|
||||
else
|
||||
{
|
||||
report(token.line, " at '" + token.lexeme + "'", message);
|
||||
}
|
||||
}
|
||||
|
||||
static void report(int line, String where, String message)
|
||||
{
|
||||
Console.WriteLine($"[line {line}] Error{where}: {message}");
|
||||
@ -102,6 +115,7 @@ public enum TokenType
|
||||
Type,
|
||||
Alias,
|
||||
Recurse,
|
||||
Def,
|
||||
Blank,
|
||||
Identifier, Number, String,
|
||||
EOF
|
||||
@ -119,6 +133,7 @@ class Scanner
|
||||
|
||||
private static readonly Dictionary<String, TokenType> keywords = new Dictionary<string, TokenType>()
|
||||
{
|
||||
{"def", TokenType.Def},
|
||||
{"when", TokenType.When},
|
||||
{"is", TokenType.Is},
|
||||
{"let", TokenType.Let},
|
||||
|
Loading…
Reference in New Issue
Block a user