376 lines
14 KiB
C#
376 lines
14 KiB
C#
using System.Globalization;
|
|
using Qrakhen.Qamp.Core.Execution;
|
|
using Qrakhen.Qamp.Core.Tokenization;
|
|
using Qrakhen.Qamp.Core.Values;
|
|
|
|
namespace Qrakhen.Qamp.Core.Compilation;
|
|
|
|
public static class ExpressionParser
|
|
{
|
|
private static readonly Dictionary<TokenType, Rule> _rules = new();
|
|
|
|
public static Rule Get(TokenType type) => _rules[type];
|
|
|
|
public static void And(Digester digester, bool canAssign)
|
|
{
|
|
long endJump = digester.EmitJump(OpCode.JumpIfFalse);
|
|
|
|
digester.Emit(OpCode.Pop);
|
|
digester.WeightedDigest(Weight.And);
|
|
|
|
digester.PatchJump(endJump);
|
|
}
|
|
|
|
static void Binary(Digester digester, bool canAssign)
|
|
{
|
|
TokenType operatorType = digester.Previous.Type;
|
|
Rule rule = Get(operatorType);
|
|
digester.WeightedDigest(rule.Weight + 1);
|
|
|
|
switch (operatorType) {
|
|
case TokenType.BangEqual:
|
|
digester.Emit(OpCode.Equal, OpCode.Not);
|
|
break;
|
|
case TokenType.EqualEqual:
|
|
digester.Emit(OpCode.Equal);
|
|
break;
|
|
case TokenType.Greater:
|
|
digester.Emit(OpCode.Greater);
|
|
break;
|
|
case TokenType.GreaterEqual:
|
|
digester.Emit(OpCode.Less, OpCode.Not);
|
|
break;
|
|
case TokenType.Less:
|
|
digester.Emit(OpCode.Less);
|
|
break;
|
|
case TokenType.LessEqual:
|
|
digester.Emit(OpCode.Greater, OpCode.Not);
|
|
break;
|
|
case TokenType.Plus:
|
|
digester.Emit(OpCode.Add);
|
|
break;
|
|
case TokenType.Minus:
|
|
digester.Emit(OpCode.Subtract);
|
|
break;
|
|
case TokenType.Star:
|
|
digester.Emit(OpCode.Multiply);
|
|
break;
|
|
case TokenType.Slash:
|
|
digester.Emit(OpCode.Divide);
|
|
break;
|
|
case TokenType.BitwiseAnd:
|
|
digester.Emit(OpCode.BitwiseAnd);
|
|
break;
|
|
case TokenType.BitwiseOr:
|
|
digester.Emit(OpCode.BitwiseOr);
|
|
break;
|
|
case TokenType.BitwiseXor:
|
|
digester.Emit(OpCode.BitwiseXor);
|
|
break;
|
|
case TokenType.BitwiseNot:
|
|
digester.Emit(OpCode.BitwiseNot);
|
|
break;
|
|
case TokenType.BitwiseLeft:
|
|
digester.Emit(OpCode.BitwiseLeft);
|
|
break;
|
|
case TokenType.BitwiseRight:
|
|
digester.Emit(OpCode.BitwiseRight);
|
|
break;
|
|
default:
|
|
throw new QampException($"Unexpected token in binary expression: {operatorType}");
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void Call(Digester digester, bool canAssign)
|
|
{
|
|
byte count = digester.Arguments();
|
|
digester.Emit(OpCode.Invoke, count);
|
|
}
|
|
|
|
static void Dot(Digester digester, bool canAssign)
|
|
{
|
|
digester.Consume(TokenType.Identifier, "Expected property name after '.'.");
|
|
long name = digester.IdentifierConstant(digester.Previous.Value);
|
|
|
|
if (canAssign && digester.Match(TokenType.Equal)) {
|
|
digester.ParseExpression();
|
|
digester.EmitDynamic(OpCode.SetMember, name);
|
|
} else if (digester.Match(TokenType.GroupOpen)) {
|
|
byte argCount = digester.Arguments();
|
|
digester.EmitDynamic(OpCode.InvokeMember, name);
|
|
digester.Emit(argCount);
|
|
} else {
|
|
digester.EmitDynamic(OpCode.GetMember, name);
|
|
}
|
|
}
|
|
|
|
static void Literal(Digester digester, bool canAssign)
|
|
{
|
|
switch (digester.Previous.Type) {
|
|
case TokenType.False:
|
|
digester.Emit(OpCode.False);
|
|
break;
|
|
case TokenType.Null:
|
|
digester.Emit(OpCode.Null);
|
|
break;
|
|
case TokenType.True:
|
|
digester.Emit(OpCode.True);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void Group(Digester digester, bool canAssign)
|
|
{
|
|
digester.ParseExpression();
|
|
digester.Consume(TokenType.GroupClose, "Expected ')' after expression.");
|
|
}
|
|
|
|
static void Array(Digester digester, bool canAssign)
|
|
{
|
|
int length = 0;
|
|
while (!digester.Check(TokenType.ArrayClose)) {
|
|
digester.ParseExpression();
|
|
length++;
|
|
if (digester.Check(TokenType.Comma))
|
|
digester.Next();
|
|
else
|
|
break;
|
|
}
|
|
digester.Consume(TokenType.ArrayClose, "Expected ']' after array declaration");
|
|
digester.EmitDynamic(OpCode.Array, length); // digester.MakeConstant(new Value((long)length)));
|
|
}
|
|
|
|
static void Accessor(Digester digester, bool canAssign)
|
|
{
|
|
digester.ParseExpression();
|
|
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
|
|
if (canAssign && digester.Match(TokenType.PlusEqual)) {
|
|
digester.ParseExpression();
|
|
digester.Emit(OpCode.AddItem);
|
|
} else if (canAssign && digester.Match(TokenType.Equal)) {
|
|
digester.ParseExpression();
|
|
digester.Emit(OpCode.SetItem);
|
|
} else {
|
|
digester.Emit(OpCode.GetItem);
|
|
}
|
|
}
|
|
|
|
static void Number(Digester digester, bool canAssign)
|
|
{
|
|
Token token = digester.Previous;
|
|
string number = token.Value ?? "";
|
|
TokenType type = token.Type;
|
|
Value value = default;
|
|
if (type == TokenType.Hexadecimal)
|
|
value = new Value(long.Parse(number, NumberStyles.HexNumber));
|
|
else if (type == TokenType.Decimal)
|
|
value = new Value(double.Parse(number, NumberStyles.Float, CultureInfo.InvariantCulture));
|
|
else if (type == TokenType.Integer)
|
|
value = new Value(long.Parse(number, NumberStyles.Integer));
|
|
else
|
|
digester.ErrorAt(token, $"Could not parse number {number}");
|
|
digester.EmitConstant(value);
|
|
}
|
|
|
|
static void Or(Digester digester, bool canAssign)
|
|
{
|
|
long elseJump = digester.EmitJump(OpCode.JumpIfFalse);
|
|
long endJump = digester.EmitJump(OpCode.Jump);
|
|
|
|
digester.PatchJump(elseJump);
|
|
digester.Emit(OpCode.Pop);
|
|
|
|
digester.WeightedDigest(Weight.Or);
|
|
digester.PatchJump(endJump);
|
|
}
|
|
|
|
static void String(Digester digester, bool canAssign)
|
|
{
|
|
digester.EmitConstant(Values.Objects.String.Make(digester.Previous.Value));
|
|
}
|
|
|
|
public static void Variable(Digester digester, string name, bool canAssign)
|
|
{
|
|
OpCode Get, Set;
|
|
long variable = digester.ResolveLocal(digester.Builder, name);
|
|
if (variable > -1) {
|
|
Get = OpCode.GetLocal;
|
|
Set = OpCode.SetLocal;
|
|
} else if ((variable = digester.ResolveOuter(digester.Builder, name)) > -1) {
|
|
Get = OpCode.GetOuter;
|
|
Set = OpCode.SetOuter;
|
|
} else {
|
|
variable = digester.IdentifierConstant(name);
|
|
Get = OpCode.GetGlobal;
|
|
Set = OpCode.SetGlobal;
|
|
}
|
|
|
|
if (canAssign && digester.Match(TokenType.Assignment, false)) {
|
|
var token = digester.Previous.Type;
|
|
|
|
if (token != TokenType.Equal) {
|
|
// Get the variable's value for the operation
|
|
digester.Emit(Get);
|
|
digester.EmitDynamic(variable);
|
|
}
|
|
|
|
if (token == TokenType.Increment || token == TokenType.Decrement) {
|
|
// In the case of atomic change, we just forge the value ourselves.
|
|
digester.EmitConstant(new Value(1L));
|
|
} else {
|
|
// ... otherwise, we expect an expression on the right-hand side - parse that.
|
|
digester.ParseExpression();
|
|
}
|
|
|
|
if (token != TokenType.Equal)
|
|
{
|
|
// Append the operator
|
|
OpCode op = token switch
|
|
{
|
|
TokenType.PlusEqual or TokenType.Increment => OpCode.Add,
|
|
TokenType.MinusEqual or TokenType.Decrement => OpCode.Subtract,
|
|
TokenType.SlashEqual => OpCode.Divide,
|
|
TokenType.StarEqual => OpCode.Multiply,
|
|
TokenType.ModuloEqual => OpCode.Modulo,
|
|
_ => throw new QampException($"Unexpected token at variable assignment: {token}")
|
|
};
|
|
digester.Emit(op);
|
|
}
|
|
|
|
// Ultimately set the new value with whatever's on stack now.
|
|
digester.Emit(Set);
|
|
digester.EmitDynamic(variable);
|
|
} else {
|
|
digester.Emit(Get);
|
|
digester.EmitDynamic(variable);
|
|
}
|
|
}
|
|
|
|
public static void Variable(Digester digester, bool canAssign)
|
|
{
|
|
Variable(digester, digester.Previous.Value!, canAssign);
|
|
}
|
|
|
|
static void Base(Digester digester, bool canAssign)
|
|
{
|
|
if (digester.ClassBuilder == null)
|
|
digester.ErrorAtPrevious("We're currently not in a class. 'base' only works inside of a class context.");
|
|
else if (digester.ClassBuilder.Outer == null)
|
|
digester.ErrorAtPrevious($"{digester.ClassBuilder.Name} is not inherited from any class, so the 'base' keyword points to nothing.");
|
|
else {
|
|
digester.Consume(TokenType.Dot, "Expected '.' after 'base' keyword.");
|
|
digester.Consume(TokenType.Identifier, "Expected an identifier after 'base' keyword.");
|
|
long name = digester.IdentifierConstant(digester.Previous.Value);
|
|
Variable(digester, "this", false);
|
|
if (digester.Match(TokenType.GroupOpen)) {
|
|
byte arguments = digester.Arguments();
|
|
Variable(digester, "base", false);
|
|
digester.Emit(OpCode.InvokeBase);
|
|
digester.Emit(arguments);
|
|
} else {
|
|
Variable(digester, "base", false);
|
|
digester.Emit(OpCode.Base);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void This(Digester digester, bool canAssign)
|
|
{
|
|
if (digester.ClassBuilder == null)
|
|
digester.ErrorAtPrevious("We're currently not in a class. 'this' only refers to the current instance of one.");
|
|
else
|
|
Variable(digester, false);
|
|
}
|
|
|
|
static void Modifier(Digester digester, bool canAssign)
|
|
{
|
|
Token token = digester.Previous;
|
|
digester.WeightedDigest(Weight.Modifier);
|
|
switch (token.Type) {
|
|
case TokenType.Bang:
|
|
digester.Emit(OpCode.Not);
|
|
break;
|
|
case TokenType.Minus:
|
|
digester.Emit(OpCode.Negate);
|
|
break;
|
|
case TokenType.BitwiseNot:
|
|
digester.Emit(OpCode.BitwiseNot);
|
|
break;
|
|
case TokenType.AddressOf:
|
|
digester.Emit(OpCode.Addr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void TypeOf(Digester digester, bool canAssign)
|
|
{
|
|
digester.TypeOf();
|
|
}
|
|
|
|
static ExpressionParser()
|
|
{
|
|
_rules[TokenType.GroupOpen] = new Rule(Group, Call, Weight.Call);
|
|
_rules[TokenType.GroupClose] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.ContextOpen] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.ContextClose] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.ArrayOpen] = new Rule(Array, Accessor, Weight.Call);
|
|
_rules[TokenType.ArrayClose] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.PlusEqual] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.MinusEqual] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Colon] = new Rule(null, Dot, Weight.Call);
|
|
_rules[TokenType.Comma] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Dot] = new Rule(null, Dot, Weight.Call);
|
|
_rules[TokenType.Minus] = new Rule(Modifier, Binary, Weight.Term);
|
|
_rules[TokenType.Plus] = new Rule(null, Binary, Weight.Term);
|
|
_rules[TokenType.Increment] = new Rule(Modifier, Binary, Weight.Call);
|
|
_rules[TokenType.Decrement] = new Rule(Modifier, Binary, Weight.Call);
|
|
_rules[TokenType.BitwiseAnd] = new Rule(Modifier, Binary, Weight.Term);
|
|
_rules[TokenType.BitwiseOr] = new Rule(null, Binary, Weight.Term);
|
|
_rules[TokenType.BitwiseXor] = new Rule(null, Binary, Weight.Term);
|
|
_rules[TokenType.BitwiseLeft] = new Rule(null, Binary, Weight.Term);
|
|
_rules[TokenType.BitwiseRight] = new Rule(null, Binary, Weight.Term);
|
|
_rules[TokenType.BitwiseNot] = new Rule(Modifier, null, Weight.Term);
|
|
_rules[TokenType.Semicolon] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Slash] = new Rule(null, Binary, Weight.Factor);
|
|
_rules[TokenType.Star] = new Rule(null, Binary, Weight.Factor);
|
|
_rules[TokenType.Bang] = new Rule(Modifier, null, Weight.None);
|
|
_rules[TokenType.BangEqual] = new Rule(null, Binary, Weight.Equal);
|
|
_rules[TokenType.Equal] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.EqualEqual] = new Rule(null, Binary, Weight.Equal);
|
|
_rules[TokenType.Greater] = new Rule(null, Binary, Weight.Compare);
|
|
_rules[TokenType.GreaterEqual] = new Rule(null, Binary, Weight.Compare);
|
|
_rules[TokenType.Less] = new Rule(null, Binary, Weight.Compare);
|
|
_rules[TokenType.LessEqual] = new Rule(null, Binary, Weight.Compare);
|
|
_rules[TokenType.Identifier] = new Rule(Variable, null, Weight.None);
|
|
_rules[TokenType.String] = new Rule(String, null, Weight.None);
|
|
_rules[TokenType.Integer] = new Rule(Number, null, Weight.None);
|
|
_rules[TokenType.Hexadecimal] = new Rule(Number, null, Weight.None);
|
|
_rules[TokenType.Decimal] = new Rule(Number, null, Weight.None);
|
|
_rules[TokenType.And] = new Rule(null, And, Weight.And);
|
|
_rules[TokenType.Class] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Else] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.False] = new Rule(Literal, null, Weight.None);
|
|
_rules[TokenType.For] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Function] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.If] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Null] = new Rule(Literal, null, Weight.None);
|
|
_rules[TokenType.Or] = new Rule(null, Or, Weight.Or);
|
|
_rules[TokenType.Print] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.TypeOf] = new Rule(TypeOf, null, Weight.None);
|
|
_rules[TokenType.AddressOf] = new Rule(Modifier, null, Weight.Term);
|
|
_rules[TokenType.Export] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Import] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Return] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Base] = new Rule(Base, null, Weight.None);
|
|
_rules[TokenType.This] = new Rule(This, null, Weight.None);
|
|
_rules[TokenType.True] = new Rule(Literal, null, Weight.None);
|
|
_rules[TokenType.Var] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.While] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Error] = new Rule(null, null, Weight.None);
|
|
_rules[TokenType.Eof] = new Rule(null, null, Weight.None);
|
|
}
|
|
}
|