feat(core/asm): add expression parser and evaluator
This commit is contained in:
1 parent
2ae19e0ee0
commit
40db2b763e
1 file changed
+208
@@ -0,0 +1,208 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Chip8.Core.Assembly;
|
||||||
|
|
||||||
|
public sealed class Expression
|
||||||
|
{
|
||||||
|
readonly IReadOnlyList<Token> tokens;
|
||||||
|
readonly Func<Token, int> resolveSymbol;
|
||||||
|
int cursor;
|
||||||
|
|
||||||
|
Expression(IReadOnlyList<Token> tokens, Func<Token, int> resolveSymbol)
|
||||||
|
{
|
||||||
|
this.tokens = tokens;
|
||||||
|
this.resolveSymbol = resolveSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int evaluate(IReadOnlyList<Token> tokens, Func<Token, int> resolveSymbol, SourcePosition position)
|
||||||
|
{
|
||||||
|
if (tokens.Count == 0)
|
||||||
|
{
|
||||||
|
throw new AsmException("expected an expression", position);
|
||||||
|
}
|
||||||
|
|
||||||
|
var parser = new Expression(tokens, resolveSymbol);
|
||||||
|
int value = parser.parseBitwiseOr();
|
||||||
|
if (parser.cursor != tokens.Count)
|
||||||
|
{
|
||||||
|
throw new AsmException("unexpected token '" + tokens[parser.cursor].text + "' in expression", tokens[parser.cursor].position);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseBitwiseOr()
|
||||||
|
{
|
||||||
|
int value = parseBitwiseXor();
|
||||||
|
while (match(TokenKind.Pipe))
|
||||||
|
{
|
||||||
|
value |= parseBitwiseXor();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseBitwiseXor()
|
||||||
|
{
|
||||||
|
int value = parseBitwiseAnd();
|
||||||
|
while (match(TokenKind.Caret))
|
||||||
|
{
|
||||||
|
value ^= parseBitwiseAnd();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseBitwiseAnd()
|
||||||
|
{
|
||||||
|
int value = parseShift();
|
||||||
|
while (match(TokenKind.Ampersand))
|
||||||
|
{
|
||||||
|
value &= parseShift();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseShift()
|
||||||
|
{
|
||||||
|
int value = parseAdditive();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (match(TokenKind.ShiftLeft))
|
||||||
|
{
|
||||||
|
value <<= parseAdditive();
|
||||||
|
}
|
||||||
|
else if (match(TokenKind.ShiftRight))
|
||||||
|
{
|
||||||
|
value >>= parseAdditive();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseAdditive()
|
||||||
|
{
|
||||||
|
int value = parseMultiplicative();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (match(TokenKind.Plus))
|
||||||
|
{
|
||||||
|
value += parseMultiplicative();
|
||||||
|
}
|
||||||
|
else if (match(TokenKind.Minus))
|
||||||
|
{
|
||||||
|
value -= parseMultiplicative();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseMultiplicative()
|
||||||
|
{
|
||||||
|
int value = parseUnary();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (match(TokenKind.Star))
|
||||||
|
{
|
||||||
|
value *= parseUnary();
|
||||||
|
}
|
||||||
|
else if (match(TokenKind.Slash))
|
||||||
|
{
|
||||||
|
int divisor = parseUnary();
|
||||||
|
if (divisor == 0)
|
||||||
|
{
|
||||||
|
throw new AsmException("division by zero", previous().position);
|
||||||
|
}
|
||||||
|
value /= divisor;
|
||||||
|
}
|
||||||
|
else if (match(TokenKind.Percent))
|
||||||
|
{
|
||||||
|
int divisor = parseUnary();
|
||||||
|
if (divisor == 0)
|
||||||
|
{
|
||||||
|
throw new AsmException("modulo by zero", previous().position);
|
||||||
|
}
|
||||||
|
value %= divisor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int parseUnary()
|
||||||
|
{
|
||||||
|
if (match(TokenKind.Minus))
|
||||||
|
{
|
||||||
|
return -parseUnary();
|
||||||
|
}
|
||||||
|
if (match(TokenKind.Plus))
|
||||||
|
{
|
||||||
|
return parseUnary();
|
||||||
|
}
|
||||||
|
if (match(TokenKind.Tilde))
|
||||||
|
{
|
||||||
|
return ~parseUnary();
|
||||||
|
}
|
||||||
|
return parsePrimary();
|
||||||
|
}
|
||||||
|
|
||||||
|
int parsePrimary()
|
||||||
|
{
|
||||||
|
Token token = advance();
|
||||||
|
|
||||||
|
if (token.kind == TokenKind.LeftParen)
|
||||||
|
{
|
||||||
|
int value = parseBitwiseOr();
|
||||||
|
expect(TokenKind.RightParen);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.kind == TokenKind.Number)
|
||||||
|
{
|
||||||
|
return token.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.kind == TokenKind.Identifier)
|
||||||
|
{
|
||||||
|
return resolveSymbol(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AsmException("expected a value but found '" + token.text + "'", token.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool match(TokenKind kind)
|
||||||
|
{
|
||||||
|
if (cursor < tokens.Count && tokens[cursor].kind == kind)
|
||||||
|
{
|
||||||
|
cursor++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token advance()
|
||||||
|
{
|
||||||
|
if (cursor >= tokens.Count)
|
||||||
|
{
|
||||||
|
throw new AsmException("unexpected end of expression", tokens[tokens.Count - 1].position);
|
||||||
|
}
|
||||||
|
return tokens[cursor++];
|
||||||
|
}
|
||||||
|
|
||||||
|
void expect(TokenKind kind)
|
||||||
|
{
|
||||||
|
if (!match(kind))
|
||||||
|
{
|
||||||
|
SourcePosition position = cursor < tokens.Count ? tokens[cursor].position : tokens[tokens.Count - 1].position;
|
||||||
|
throw new AsmException("expected '" + kind + "'", position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Token previous() => tokens[cursor - 1];
|
||||||
|
}
|
||||||
Reference in new issue
Block a user