Parse a subset of expressions
This commit is contained in:
parent
b9b8c8e24b
commit
77b1fab74f
@ -6,7 +6,7 @@ namespace Finn;
|
|||||||
|
|
||||||
class ASTPrinter : IVisitor<string>
|
class ASTPrinter : IVisitor<string>
|
||||||
{
|
{
|
||||||
string print(Expr expr)
|
public string print(Expr expr)
|
||||||
{
|
{
|
||||||
return expr.accept(this);
|
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;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Finn.AST;
|
||||||
|
|
||||||
namespace Finn;
|
namespace Finn;
|
||||||
|
|
||||||
@ -36,11 +36,12 @@ class Program
|
|||||||
{
|
{
|
||||||
var scanner = new Scanner(src);
|
var scanner = new Scanner(src);
|
||||||
List<Token> tokens = scanner.scanTokens();
|
List<Token> tokens = scanner.scanTokens();
|
||||||
|
Parser parser = new Parser(tokens);
|
||||||
|
Expr? expression = parser.parse();
|
||||||
|
|
||||||
foreach (Token token in tokens)
|
if (hadError || expression == null) return;
|
||||||
{
|
|
||||||
Console.WriteLine(token);
|
Console.WriteLine(new ASTPrinter().print(expression));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void runPrompt()
|
static void runPrompt()
|
||||||
@ -62,6 +63,18 @@ class Program
|
|||||||
report(line, "", message);
|
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)
|
static void report(int line, String where, String message)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[line {line}] Error{where}: {message}");
|
Console.WriteLine($"[line {line}] Error{where}: {message}");
|
||||||
@ -102,6 +115,7 @@ public enum TokenType
|
|||||||
Type,
|
Type,
|
||||||
Alias,
|
Alias,
|
||||||
Recurse,
|
Recurse,
|
||||||
|
Def,
|
||||||
Blank,
|
Blank,
|
||||||
Identifier, Number, String,
|
Identifier, Number, String,
|
||||||
EOF
|
EOF
|
||||||
@ -119,6 +133,7 @@ class Scanner
|
|||||||
|
|
||||||
private static readonly Dictionary<String, TokenType> keywords = new Dictionary<string, TokenType>()
|
private static readonly Dictionary<String, TokenType> keywords = new Dictionary<string, TokenType>()
|
||||||
{
|
{
|
||||||
|
{"def", TokenType.Def},
|
||||||
{"when", TokenType.When},
|
{"when", TokenType.When},
|
||||||
{"is", TokenType.Is},
|
{"is", TokenType.Is},
|
||||||
{"let", TokenType.Let},
|
{"let", TokenType.Let},
|
||||||
|
Loading…
Reference in New Issue
Block a user