feat(octoasm): add Octo constant expression evaluator
This commit is contained in:
1 parent
da135491c8
commit
f7f26720cb
1 file changed
+362
@@ -0,0 +1,362 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Chip8.OctoAsm;
|
||||
|
||||
public sealed class OctoConstEvaluator
|
||||
{
|
||||
readonly IReadOnlyList<OctoToken> tokens;
|
||||
readonly int end;
|
||||
readonly Func<OctoToken, double> resolveName;
|
||||
int cursor;
|
||||
|
||||
OctoConstEvaluator(IReadOnlyList<OctoToken> tokens, int start, int end, Func<OctoToken, double> resolveName)
|
||||
{
|
||||
this.tokens = tokens;
|
||||
this.cursor = start;
|
||||
this.end = end;
|
||||
this.resolveName = resolveName;
|
||||
}
|
||||
|
||||
public static double evaluate(
|
||||
IReadOnlyList<OctoToken> tokens, int start, int end, Func<OctoToken, double> resolveName, SourcePosition where)
|
||||
{
|
||||
if (start >= end)
|
||||
{
|
||||
throw error("expected an expression", where);
|
||||
}
|
||||
|
||||
var evaluator = new OctoConstEvaluator(tokens, start, end, resolveName);
|
||||
double value = evaluator.parseLogicalOr();
|
||||
if (evaluator.cursor != end)
|
||||
{
|
||||
OctoToken leftover = evaluator.tokens[evaluator.cursor];
|
||||
throw error("unexpected token " + leftover + " in expression", leftover.position);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseLogicalOr()
|
||||
{
|
||||
double value = parseLogicalAnd();
|
||||
while (matchWord("||"))
|
||||
{
|
||||
double right = parseLogicalAnd();
|
||||
value = truthy(value) || truthy(right) ? 1 : 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseLogicalAnd()
|
||||
{
|
||||
double value = parseEquality();
|
||||
while (matchWord("&&"))
|
||||
{
|
||||
double right = parseEquality();
|
||||
value = truthy(value) && truthy(right) ? 1 : 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseEquality()
|
||||
{
|
||||
double value = parseRelational();
|
||||
while (true)
|
||||
{
|
||||
if (matchWord("=="))
|
||||
{
|
||||
value = value == parseRelational() ? 1 : 0;
|
||||
}
|
||||
else if (matchWord("!="))
|
||||
{
|
||||
value = value != parseRelational() ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double parseRelational()
|
||||
{
|
||||
double value = parseBitOr();
|
||||
while (true)
|
||||
{
|
||||
if (matchWord("<="))
|
||||
{
|
||||
value = value <= parseBitOr() ? 1 : 0;
|
||||
}
|
||||
else if (matchWord(">="))
|
||||
{
|
||||
value = value >= parseBitOr() ? 1 : 0;
|
||||
}
|
||||
else if (matchWord("<"))
|
||||
{
|
||||
value = value < parseBitOr() ? 1 : 0;
|
||||
}
|
||||
else if (matchWord(">"))
|
||||
{
|
||||
value = value > parseBitOr() ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double parseBitOr()
|
||||
{
|
||||
double value = parseBitXor();
|
||||
while (matchWord("|"))
|
||||
{
|
||||
value = toInt(value) | toInt(parseBitXor());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseBitXor()
|
||||
{
|
||||
double value = parseBitAnd();
|
||||
while (matchWord("^"))
|
||||
{
|
||||
value = toInt(value) ^ toInt(parseBitAnd());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseBitAnd()
|
||||
{
|
||||
double value = parseShift();
|
||||
while (matchWord("&"))
|
||||
{
|
||||
value = toInt(value) & toInt(parseShift());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseShift()
|
||||
{
|
||||
double value = parseAdditive();
|
||||
while (true)
|
||||
{
|
||||
if (matchWord("<<"))
|
||||
{
|
||||
value = toInt(value) << (int)toInt(parseAdditive());
|
||||
}
|
||||
else if (matchWord(">>"))
|
||||
{
|
||||
value = toInt(value) >> (int)toInt(parseAdditive());
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double parseAdditive()
|
||||
{
|
||||
double value = parseMultiplicative();
|
||||
while (true)
|
||||
{
|
||||
if (matchWord("+"))
|
||||
{
|
||||
value += parseMultiplicative();
|
||||
}
|
||||
else if (matchWord("-"))
|
||||
{
|
||||
value -= parseMultiplicative();
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double parseMultiplicative()
|
||||
{
|
||||
double value = parseMinMax();
|
||||
while (true)
|
||||
{
|
||||
if (matchWord("*"))
|
||||
{
|
||||
value *= parseMinMax();
|
||||
}
|
||||
else if (matchWord("/"))
|
||||
{
|
||||
double divisor = parseMinMax();
|
||||
if (divisor == 0)
|
||||
{
|
||||
throw error("division by zero", previous().position);
|
||||
}
|
||||
value /= divisor;
|
||||
}
|
||||
else if (matchWord("%"))
|
||||
{
|
||||
long divisor = toInt(parseMinMax());
|
||||
if (divisor == 0)
|
||||
{
|
||||
throw error("modulo by zero", previous().position);
|
||||
}
|
||||
value = toInt(value) % divisor;
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double parseMinMax()
|
||||
{
|
||||
double value = parsePower();
|
||||
while (true)
|
||||
{
|
||||
if (matchWord("min"))
|
||||
{
|
||||
value = Math.Min(value, parsePower());
|
||||
}
|
||||
else if (matchWord("max"))
|
||||
{
|
||||
value = Math.Max(value, parsePower());
|
||||
}
|
||||
else
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double parsePower()
|
||||
{
|
||||
double value = parseUnary();
|
||||
if (matchWord("**") || matchWord("pow"))
|
||||
{
|
||||
return Math.Pow(value, parsePower());
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
double parseUnary()
|
||||
{
|
||||
if (matchWord("-"))
|
||||
{
|
||||
return -parseUnary();
|
||||
}
|
||||
if (matchWord("+"))
|
||||
{
|
||||
return parseUnary();
|
||||
}
|
||||
if (matchWord("~"))
|
||||
{
|
||||
return ~toInt(parseUnary());
|
||||
}
|
||||
if (matchWord("!"))
|
||||
{
|
||||
return truthy(parseUnary()) ? 0 : 1;
|
||||
}
|
||||
if (peekIsFunction(out string function))
|
||||
{
|
||||
advance();
|
||||
return applyFunction(function, parseUnary());
|
||||
}
|
||||
return parsePrimary();
|
||||
}
|
||||
|
||||
double parsePrimary()
|
||||
{
|
||||
OctoToken token = advance();
|
||||
|
||||
if (token.kind == OctoTokenKind.OpenParen)
|
||||
{
|
||||
double value = parseLogicalOr();
|
||||
expect(OctoTokenKind.CloseParen);
|
||||
return value;
|
||||
}
|
||||
|
||||
if (token.kind == OctoTokenKind.Number)
|
||||
{
|
||||
return token.value;
|
||||
}
|
||||
|
||||
if (token.kind == OctoTokenKind.Word)
|
||||
{
|
||||
return resolveName(token);
|
||||
}
|
||||
|
||||
throw error("expected a value but found " + token, token.position);
|
||||
}
|
||||
|
||||
static double applyFunction(string name, double argument) => name switch
|
||||
{
|
||||
"sqrt" => Math.Sqrt(argument),
|
||||
"sin" => Math.Sin(argument),
|
||||
"cos" => Math.Cos(argument),
|
||||
"tan" => Math.Tan(argument),
|
||||
"exp" => Math.Exp(argument),
|
||||
"log" => Math.Log(argument),
|
||||
"abs" => Math.Abs(argument),
|
||||
"sign" => Math.Sign(argument),
|
||||
"ceil" => Math.Ceiling(argument),
|
||||
"floor" => Math.Floor(argument),
|
||||
_ => throw new OctoCompileException(
|
||||
new OctoDiagnostic(OctoSeverity.Error, "unknown function '" + name + "'", new SourcePosition(0, 0))),
|
||||
};
|
||||
|
||||
static readonly HashSet<string> functionNames = new()
|
||||
{
|
||||
"sqrt", "sin", "cos", "tan", "exp", "log", "abs", "sign", "ceil", "floor",
|
||||
};
|
||||
|
||||
bool peekIsFunction(out string function)
|
||||
{
|
||||
if (cursor < end && tokens[cursor].kind == OctoTokenKind.Word && functionNames.Contains(tokens[cursor].text))
|
||||
{
|
||||
function = tokens[cursor].text;
|
||||
return true;
|
||||
}
|
||||
function = "";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matchWord(string word)
|
||||
{
|
||||
if (cursor < end && tokens[cursor].kind == OctoTokenKind.Word && tokens[cursor].text == word)
|
||||
{
|
||||
cursor++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
OctoToken advance()
|
||||
{
|
||||
if (cursor >= end)
|
||||
{
|
||||
throw error("unexpected end of expression", previous().position);
|
||||
}
|
||||
return tokens[cursor++];
|
||||
}
|
||||
|
||||
void expect(OctoTokenKind kind)
|
||||
{
|
||||
if (cursor >= end || tokens[cursor].kind != kind)
|
||||
{
|
||||
SourcePosition where = cursor < end ? tokens[cursor].position : previous().position;
|
||||
throw error("expected " + kind, where);
|
||||
}
|
||||
cursor++;
|
||||
}
|
||||
|
||||
OctoToken previous() => tokens[cursor > 0 ? cursor - 1 : 0];
|
||||
|
||||
static long toInt(double value) => (long)value;
|
||||
|
||||
static bool truthy(double value) => value != 0;
|
||||
|
||||
static OctoCompileException error(string message, SourcePosition where) =>
|
||||
new(new OctoDiagnostic(OctoSeverity.Error, message, where));
|
||||
}
|
||||
Reference in new issue
Block a user