Skip to content

Commit

Permalink
Implement evaluation of assignments/expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanmoffat committed Jan 28, 2022
1 parent 9f25a89 commit 8d7f154
Show file tree
Hide file tree
Showing 17 changed files with 396 additions and 12 deletions.
10 changes: 10 additions & 0 deletions EOBot/EOBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Extensions.DependencyInjection, Version=3.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.3.1.3\lib\net461\Microsoft.Extensions.DependencyInjection.dll</HintPath>
</Reference>
Expand Down Expand Up @@ -97,15 +98,24 @@
<Compile Include="Interpreter\BotToken.cs" />
<Compile Include="Interpreter\BotTokenParser.cs" />
<Compile Include="Interpreter\BotTokenType.cs" />
<Compile Include="Interpreter\Extensions\ProgramStateExtensions.cs" />
<Compile Include="Interpreter\IdentifierBotToken.cs" />
<Compile Include="Interpreter\States\AssignmentEvaluator.cs" />
<Compile Include="Interpreter\States\ExpressionEvaluator.cs" />
<Compile Include="Interpreter\States\ExpressionTailEvaluator.cs" />
<Compile Include="Interpreter\States\FunctionEvaluator.cs" />
<Compile Include="Interpreter\States\IScriptEvaluator.cs" />
<Compile Include="Interpreter\States\KeywordEvaluator.cs" />
<Compile Include="Interpreter\States\LabelEvaluator.cs" />
<Compile Include="Interpreter\States\OperandEvaluator.cs" />
<Compile Include="Interpreter\States\ProgramState.cs" />
<Compile Include="Interpreter\States\ScriptEvaluator.cs" />
<Compile Include="Interpreter\States\StatementEvaluator.cs" />
<Compile Include="Interpreter\States\StatementListEvaluator.cs" />
<Compile Include="Interpreter\States\VariableEvaluator.cs" />
<Compile Include="Interpreter\Variables\ArrayVariable.cs" />
<Compile Include="Interpreter\Variables\BoolVariable.cs" />
<Compile Include="Interpreter\VariableBotToken.cs" />
<Compile Include="Interpreter\Variables\VoidFunction.cs" />
<Compile Include="Interpreter\Variables\Function.cs" />
<Compile Include="Interpreter\Variables\ICallable.cs" />
Expand Down
4 changes: 4 additions & 0 deletions EOBot/Interpreter/BotTokenParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ public BotToken GetNextToken()

return new BotToken(BotTokenType.Identifier, identifier);
}
case '+': return new BotToken(BotTokenType.PlusOperator, inputChar.ToString());
case '-': return new BotToken(BotTokenType.MinusOperator, inputChar.ToString());
case '*': return new BotToken(BotTokenType.MultiplyOperator, inputChar.ToString());
case '/': return new BotToken(BotTokenType.DivideOperator, inputChar.ToString());
default: return new BotToken(BotTokenType.Error, inputChar.ToString());
}
}
Expand Down
4 changes: 4 additions & 0 deletions EOBot/Interpreter/BotTokenType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public enum BotTokenType
GreaterThanOperator,
GreaterThanEqOperator,
NotEqualOperator,
PlusOperator,
MinusOperator,
MultiplyOperator,
DivideOperator,
NewLine,
Error
}
Expand Down
20 changes: 20 additions & 0 deletions EOBot/Interpreter/Extensions/ProgramStateExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using EOBot.Interpreter.States;

namespace EOBot.Interpreter.Extensions
{
public static class ProgramStateExtensions
{
public static bool MatchOneOf(this ProgramState input, params BotTokenType[] tokenTypes)
{
// todo: there's probably LINQ for this but I'm tired and my brain isn't quite working
// needs to stop trying to match after first match, can't use FirstOrDefault unless default(BotTokenType) can be used as an "empty" value
foreach (var type in tokenTypes)
{
if (input.Match(type))
return true;
}

return false;
}
}
}
13 changes: 13 additions & 0 deletions EOBot/Interpreter/IdentifierBotToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace EOBot.Interpreter
{
public class IdentifierBotToken : BotToken
{
public int? ArrayIndex { get; }

public IdentifierBotToken(BotTokenType tokenType, string tokenValue, int? arrayIndex = null)
: base(tokenType, tokenValue)
{
ArrayIndex = arrayIndex;
}
}
}
42 changes: 37 additions & 5 deletions EOBot/Interpreter/States/AssignmentEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Generic;
using EOBot.Interpreter.Variables;
using System.Collections.Generic;
using System.Linq;

namespace EOBot.Interpreter.States
{
Expand All @@ -13,10 +15,40 @@ public AssignmentEvaluator(IEnumerable<IScriptEvaluator> evaluators)

public bool Evaluate(ProgramState input)
{
// match variable
// match =
// match expr
throw new System.NotImplementedException();
if (!_evaluators.OfType<VariableEvaluator>().Single().Evaluate(input) ||
!input.Match(BotTokenType.AssignOperator) ||
!_evaluators.OfType<ExpressionEvaluator>().Single().Evaluate(input))
{
return false;
}

var expressionResult = (VariableBotToken)input.OperationStack.Pop();
if (input.OperationStack.Count == 0)
return false;

// todo: check that assignOp is an assignment operator
var assignOp = input.OperationStack.Pop();
if (input.OperationStack.Count == 0)
return false;

var variable = (IdentifierBotToken)input.OperationStack.Pop();
if (input.OperationStack.Count == 0)
return false;

if (variable.ArrayIndex != null)
{
if (!input.SymbolTable.ContainsKey(variable.TokenValue))
return false;

((ArrayVariable)input.SymbolTable[variable.TokenValue]).Value[variable.ArrayIndex.Value] = expressionResult.VariableValue;
}
else
{
// todo: dynamic typing with no warning, or warn if changing typing of variable on assignment?
input.SymbolTable[variable.TokenValue] = expressionResult.VariableValue;
}

return true;
}
}
}
123 changes: 123 additions & 0 deletions EOBot/Interpreter/States/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using EOBot.Interpreter.Variables;
using System.Collections.Generic;
using System.Linq;

namespace EOBot.Interpreter.States
{
public class ExpressionEvaluator : IScriptEvaluator
{
private readonly IEnumerable<IScriptEvaluator> _evaluators;

public ExpressionEvaluator(IEnumerable<IScriptEvaluator> evaluators)
{
_evaluators = evaluators;
}

public bool Evaluate(ProgramState input)
{
var rParenExpected = input.Match(BotTokenType.LParen);

if (!_evaluators.OfType<OperandEvaluator>().Single().Evaluate(input))
return false;

// expression may just be a single result (since expressions are recursive - doesn't have to be 'A op B')
if (!_evaluators.OfType<ExpressionTailEvaluator>().Single().Evaluate(input))
{
if (rParenExpected && !input.Expect(BotTokenType.RParen))
return false;

if (input.OperationStack.Count == 0)
return false;

// convert to variable token (resolve identifier) so consumer of expression result can use it
var singleOperand = GetOperand(input);
input.OperationStack.Push(singleOperand);

return true;
}

if (rParenExpected && !input.Expect(BotTokenType.RParen))
return false;

if (input.OperationStack.Count == 0)
return false;
var operand2 = GetOperand(input);

if (input.OperationStack.Count == 0)
return false;
var @operator = input.OperationStack.Pop();

if (input.OperationStack.Count == 0)
return false;
var operand1 = GetOperand(input);

IVariable expressionResult;
switch (@operator.TokenType)
{
case BotTokenType.EqualOperator: expressionResult = new BoolVariable(operand1.VariableValue.Equals(operand2.VariableValue)); break;
case BotTokenType.NotEqualOperator: expressionResult = new BoolVariable(!operand1.VariableValue.Equals(operand2.VariableValue)); break;
case BotTokenType.LessThanOperator: expressionResult = new BoolVariable(operand1.VariableValue.CompareTo(operand2.VariableValue) < 0); break;
case BotTokenType.GreaterThanOperator: expressionResult = new BoolVariable(operand1.VariableValue.CompareTo(operand2.VariableValue) > 0); break;
case BotTokenType.LessThanEqOperator: expressionResult = new BoolVariable(operand1.VariableValue.CompareTo(operand2.VariableValue) <= 0); break;
case BotTokenType.GreaterThanEqOperator: expressionResult = new BoolVariable(operand1.VariableValue.CompareTo(operand2.VariableValue) >= 0); break;
case BotTokenType.PlusOperator: expressionResult = Add((dynamic)operand1.VariableValue, (dynamic)operand2.VariableValue); break;
case BotTokenType.MinusOperator: expressionResult = Subtract((dynamic)operand1.VariableValue, (dynamic)operand2.VariableValue); break;
case BotTokenType.MultiplyOperator: expressionResult = Multiply((dynamic)operand1.VariableValue, (dynamic)operand2.VariableValue); break;
case BotTokenType.DivideOperator: expressionResult = Divide((dynamic)operand1.VariableValue, (dynamic)operand2.VariableValue); break;
default: return false;
}

if (rParenExpected)
{
if (input.OperationStack.Count == 0)
return false;

// todo: check that this is the LPAREN
input.OperationStack.Pop();
}

// todo: indicate errors in the operation
if (expressionResult == null)
return false;

input.OperationStack.Push(new VariableBotToken(BotTokenType.Literal, expressionResult.StringValue, expressionResult));

return true;
}

private static VariableBotToken GetOperand(ProgramState input)
{
var operand = input.OperationStack.Peek() as VariableBotToken;

if (operand == null)
{
var identifier = (IdentifierBotToken)input.OperationStack.Pop();
var variableValue = (IVariable)input.SymbolTable[identifier.TokenValue];
if (identifier.ArrayIndex != null)
variableValue = ((ArrayVariable)variableValue).Value[identifier.ArrayIndex.Value];
operand = new VariableBotToken(BotTokenType.Literal, variableValue.ToString(), variableValue);
}
else
{
input.OperationStack.Pop();
}

return operand;
}

private IIdentifiable Add(IntVariable a, IntVariable b) => new IntVariable(a.Value + b.Value);
private IIdentifiable Add(IntVariable a, StringVariable b) => new StringVariable(a.Value + b.Value);
private IIdentifiable Add(StringVariable a, IntVariable b) => new StringVariable(a.Value + b.Value);
private IIdentifiable Add(StringVariable a, StringVariable b) => new StringVariable(a.Value + b.Value);
private IIdentifiable Add(object a, object b) => null;

private IIdentifiable Subtract(IntVariable a, IntVariable b) => new IntVariable(a.Value - b.Value);
private IIdentifiable Subtract(object a, object b) => null;

private IIdentifiable Multiply(IntVariable a, IntVariable b) => new IntVariable(a.Value * b.Value);
private IIdentifiable Multiply(object a, object b) => null;

private IIdentifiable Divide(IntVariable a, IntVariable b) => new IntVariable(a.Value / b.Value);
private IIdentifiable Divide(object a, object b) => null;
}
}
32 changes: 32 additions & 0 deletions EOBot/Interpreter/States/ExpressionTailEvaluator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using EOBot.Interpreter.Extensions;
using System.Collections.Generic;
using System.Linq;

namespace EOBot.Interpreter.States
{
public class ExpressionTailEvaluator : IScriptEvaluator
{
private readonly IEnumerable<IScriptEvaluator> _evaluators;

public ExpressionTailEvaluator(IEnumerable<IScriptEvaluator> evaluators)
{
_evaluators = evaluators;
}

public bool Evaluate(ProgramState input)
{
return input.MatchOneOf(
BotTokenType.EqualOperator,
BotTokenType.NotEqualOperator,
BotTokenType.GreaterThanOperator,
BotTokenType.LessThanOperator,
BotTokenType.GreaterThanEqOperator,
BotTokenType.LessThanEqOperator,
BotTokenType.PlusOperator,
BotTokenType.MinusOperator,
BotTokenType.MultiplyOperator,
BotTokenType.DivideOperator)
&& _evaluators.OfType<ExpressionEvaluator>().Single().Evaluate(input);
}
}
}
21 changes: 21 additions & 0 deletions EOBot/Interpreter/States/OperandEvaluator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Linq;

namespace EOBot.Interpreter.States
{
public class OperandEvaluator : IScriptEvaluator
{
private readonly IEnumerable<IScriptEvaluator> _evaluators;

public OperandEvaluator(IEnumerable<IScriptEvaluator> evaluators)
{
_evaluators = evaluators;
}

public bool Evaluate(ProgramState input)
{
return _evaluators.OfType<VariableEvaluator>().Single().Evaluate(input) ||
input.Match(BotTokenType.Literal);
}
}
}
14 changes: 13 additions & 1 deletion EOBot/Interpreter/States/ProgramState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,26 @@ public ProgramState(IReadOnlyList<BotToken> program)
ExecutionIndex = 0;
}

/// <summary>
/// Check for a token at the program's execution index. If it is the expected type, increment execution index.
/// </summary>
public bool Expect(BotTokenType tokenType)
{
if (ExecutionIndex >= Program.Count)
return false;

return Program[ExecutionIndex].TokenType == tokenType;
if (Program[ExecutionIndex].TokenType == tokenType)
{
ExecutionIndex++;
return true;
}

return false;
}

/// <summary>
/// Check for a token at the programs execution index. If it is the expected type, push it onto the operation stack and increment execution index.
/// </summary>
public bool Match(BotTokenType tokenType)
{
if (ExecutionIndex >= Program.Count)
Expand Down
46 changes: 46 additions & 0 deletions EOBot/Interpreter/States/VariableEvaluator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using EOBot.Interpreter.Variables;
using System.Collections.Generic;
using System.Linq;

namespace EOBot.Interpreter.States
{
public class VariableEvaluator : IScriptEvaluator
{
private readonly IEnumerable<IScriptEvaluator> _evaluators;

public VariableEvaluator(IEnumerable<IScriptEvaluator> evaluators)
{
_evaluators = evaluators;
}

public bool Evaluate(ProgramState input)
{
if (!input.Match(BotTokenType.Identifier))
return false;

int? arrayIndex = null;

if (input.Expect(BotTokenType.LBracket))
{
if (!_evaluators.OfType<ExpressionEvaluator>().Single().Evaluate(input))
return false;
input.Expect(BotTokenType.RBracket);

if (input.OperationStack.Count == 0)
return false;
var expressionResult = (VariableBotToken)input.OperationStack.Pop();

// todo: error checking if expression doesn't evaluate down to int
arrayIndex = ((IntVariable)expressionResult.VariableValue).Value;
}

if (input.OperationStack.Count == 0)
return false;
var identifier = input.OperationStack.Pop();

input.OperationStack.Push(new IdentifierBotToken(BotTokenType.Identifier, identifier.TokenValue, arrayIndex));

return true;
}
}
}

0 comments on commit 8d7f154

Please sign in to comment.