Skip to content

Commit

Permalink
Make interpreter run async. Add built-in sleep function. Add delay an…
Browse files Browse the repository at this point in the history
…d automatic disconnect for scripted bots.
  • Loading branch information
ethanmoffat committed Feb 1, 2022
1 parent d0e5285 commit e729a0e
Show file tree
Hide file tree
Showing 28 changed files with 576 additions and 141 deletions.
2 changes: 2 additions & 0 deletions EOBot/EOBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,10 @@
<Compile Include="Interpreter\Variables\ArrayVariable.cs" />
<Compile Include="Interpreter\Variables\BoolVariable.cs" />
<Compile Include="Interpreter\VariableBotToken.cs" />
<Compile Include="Interpreter\Variables\AsyncFunction.cs" />
<Compile Include="Interpreter\Variables\PredefinedIdentifiers.cs" />
<Compile Include="Interpreter\Variables\UndefinedVariable.cs" />
<Compile Include="Interpreter\Variables\AsyncVoidFunction.cs" />
<Compile Include="Interpreter\Variables\VoidFunction.cs" />
<Compile Include="Interpreter\Variables\Function.cs" />
<Compile Include="Interpreter\Variables\ICallable.cs" />
Expand Down
5 changes: 3 additions & 2 deletions EOBot/Interpreter/BotInterpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

namespace EOBot.Interpreter
{
Expand Down Expand Up @@ -52,7 +53,7 @@ internal ProgramState Prepare(int botIndex, ArgumentsParser parsedArgs, IReadOnl
return input;
}

public void Run(ProgramState programState)
public async Task Run(ProgramState programState)
{
var evaluators = new List<IScriptEvaluator>();
evaluators.Add(new StatementListEvaluator(evaluators));
Expand All @@ -71,7 +72,7 @@ public void Run(ProgramState programState)

var scriptEvaluator = new ScriptEvaluator(evaluators);

if (!scriptEvaluator.Evaluate(programState))
if (!await scriptEvaluator.EvaluateAsync(programState))
{
ConsoleHelper.WriteMessage(ConsoleHelper.Type.Error, "Error during execution! //todo: better error handling", ConsoleColor.Red);
}
Expand Down
88 changes: 60 additions & 28 deletions EOBot/Interpreter/BuiltInIdentifierConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace EOBot.Interpreter
{
Expand All @@ -30,26 +32,26 @@ public BuiltInIdentifierConfigurator(ProgramState state, int botIndex, Arguments

public void SetupBuiltInFunctions()
{
var printFunc = new VoidFunctionRef<object>(PredefinedIdentifiers.PRINT_FUNC, param1 => ConsoleHelper.WriteMessage(ConsoleHelper.Type.None, param1.ToString()));
_state.SymbolTable[PredefinedIdentifiers.PRINT_FUNC] = (true, printFunc);

var lenFunc = new FunctionRef<ArrayVariable, int>(PredefinedIdentifiers.LEN_FUNC, param1 => param1.Value.Count);
_state.SymbolTable[PredefinedIdentifiers.LEN_FUNC] = (true, lenFunc);

var arrayFunc = new FunctionRef<int, List<IVariable>>(PredefinedIdentifiers.ARRAY_FUNC,
param1 => Enumerable.Repeat(UndefinedVariable.Instance, param1).Cast<IVariable>().ToList());
_state.SymbolTable[PredefinedIdentifiers.ARRAY_FUNC] = (true, arrayFunc);

BotSetup();
_state.SymbolTable[PredefinedIdentifiers.CONNECT_FUNC] = (true, new VoidFunctionRef<string, int>(PredefinedIdentifiers.CONNECT_FUNC, ConnectDefinition));
_state.SymbolTable[PredefinedIdentifiers.DISCONNECT_FUNC] = (true, new VoidFunction(PredefinedIdentifiers.DISCONNECT_FUNC, DisconnectDefinition));
_state.SymbolTable[PredefinedIdentifiers.CREATE_ACCOUNT_FUNC] = (true, new FunctionRef<string, string, int>(PredefinedIdentifiers.CREATE_ACCOUNT_FUNC, (user, pass) => (int)_botHelper.CreateAccountAsync(user, pass).GetAwaiter().GetResult()));
_state.SymbolTable[PredefinedIdentifiers.LOGIN_FUNC] = (true, new FunctionRef<string, string, int>(PredefinedIdentifiers.LOGIN_FUNC, (user, pass) => (int)_botHelper.LoginToAccountAsync(user, pass).GetAwaiter().GetResult()));
_state.SymbolTable[PredefinedIdentifiers.CREATE_AND_LOGIN_FUNC] = (true, new FunctionRef<string, string, int>(PredefinedIdentifiers.CREATE_AND_LOGIN_FUNC, CreateAndLoginDefinition));
_state.SymbolTable[PredefinedIdentifiers.CHANGE_PASS_FUNC] = (true, new FunctionRef<string, string, string, int>(PredefinedIdentifiers.CHANGE_PASS_FUNC, (user, oldPass, newPass) => (int)_botHelper.ChangePasswordAsync(user, oldPass, newPass).GetAwaiter().GetResult()));
_state.SymbolTable[PredefinedIdentifiers.CREATE_CHARACTER_FUNC] = (true, new FunctionRef<string, int>(PredefinedIdentifiers.CREATE_CHARACTER_FUNC, name => (int)_botHelper.CreateCharacterAsync(name).GetAwaiter().GetResult()));
_state.SymbolTable[PredefinedIdentifiers.DELETE_CHARACTER_FUNC] = (true, new FunctionRef<string, bool, int>(PredefinedIdentifiers.DELETE_CHARACTER_FUNC, (name, force) => (int)_botHelper.DeleteCharacterAsync(name, force).GetAwaiter().GetResult()));
_state.SymbolTable[PredefinedIdentifiers.LOGIN_CHARACTER_FUNC] = (true, new VoidFunctionRef<string>(PredefinedIdentifiers.LOGIN_CHARACTER_FUNC, name => _botHelper.LoginToCharacterAsync(name).GetAwaiter().GetResult()));
_state.SymbolTable[PredefinedIdentifiers.PRINT_FUNC] = Readonly(new VoidFunction<object>(PredefinedIdentifiers.PRINT_FUNC, param1 => ConsoleHelper.WriteMessage(ConsoleHelper.Type.None, param1.ToString())));
_state.SymbolTable[PredefinedIdentifiers.LEN_FUNC] = Readonly(new Function<ArrayVariable, int>(PredefinedIdentifiers.LEN_FUNC, param1 => param1.Value.Count));
_state.SymbolTable[PredefinedIdentifiers.ARRAY_FUNC] = Readonly(new Function<int, List<IVariable>>(PredefinedIdentifiers.ARRAY_FUNC, param1 => Enumerable.Repeat(UndefinedVariable.Instance, param1).Cast<IVariable>().ToList()));
_state.SymbolTable[PredefinedIdentifiers.SLEEP] = Readonly(new VoidFunction<int>(PredefinedIdentifiers.SLEEP, param1 => Thread.Sleep(param1)));

BotDependencySetup();
_state.SymbolTable[PredefinedIdentifiers.CONNECT_FUNC] = Readonly(new AsyncVoidFunction<string, int>(PredefinedIdentifiers.CONNECT_FUNC, ConnectAsync));
_state.SymbolTable[PredefinedIdentifiers.DISCONNECT_FUNC] = Readonly(new VoidFunction(PredefinedIdentifiers.DISCONNECT_FUNC, Disconnect));
_state.SymbolTable[PredefinedIdentifiers.CREATE_ACCOUNT_FUNC] = Readonly(new AsyncFunction<string, string, int>(PredefinedIdentifiers.CREATE_ACCOUNT_FUNC, CreateAccountAsync));
_state.SymbolTable[PredefinedIdentifiers.LOGIN_FUNC] = Readonly(new AsyncFunction<string, string, int>(PredefinedIdentifiers.LOGIN_FUNC, LoginAsync));
_state.SymbolTable[PredefinedIdentifiers.CREATE_AND_LOGIN_FUNC] = Readonly(new AsyncFunction<string, string, int>(PredefinedIdentifiers.CREATE_AND_LOGIN_FUNC, CreateAndLoginAsync));
_state.SymbolTable[PredefinedIdentifiers.CHANGE_PASS_FUNC] = Readonly(new AsyncFunction<string, string, string, int>(PredefinedIdentifiers.CHANGE_PASS_FUNC, ChangePasswordAsync));
_state.SymbolTable[PredefinedIdentifiers.CREATE_CHARACTER_FUNC] = Readonly(new AsyncFunction<string, int>(PredefinedIdentifiers.CREATE_CHARACTER_FUNC, CreateCharacterAsync));
_state.SymbolTable[PredefinedIdentifiers.DELETE_CHARACTER_FUNC] = Readonly(new AsyncFunction<string, bool, int>(PredefinedIdentifiers.DELETE_CHARACTER_FUNC, DeleteCharacterAsync));
_state.SymbolTable[PredefinedIdentifiers.LOGIN_CHARACTER_FUNC] = Readonly(new AsyncVoidFunction<string>(PredefinedIdentifiers.LOGIN_CHARACTER_FUNC, LoginToCharacterAsync));
}

private static (bool, IIdentifiable) Readonly(IIdentifiable identifiable)
{
return (true, identifiable);
}

public void SetupBuiltInVariables()
Expand All @@ -71,15 +73,15 @@ public void SetupBuiltInVariables()
_state.SymbolTable[PredefinedIdentifiers.MAPSTATE] = (true, UndefinedVariable.Instance);
}

private void BotSetup()
private void BotDependencySetup()
{
var c = DependencyMaster.TypeRegistry[_botIndex];
var networkClientRepository = c.Resolve<INetworkClientRepository>();
var networkClientFactory = c.Resolve<INetworkClientFactory>();
networkClientRepository.NetworkClient = networkClientFactory.CreateNetworkClient();
}

private void ConnectDefinition(string host, int port)
private async Task ConnectAsync(string host, int port)
{
var c = DependencyMaster.TypeRegistry[_botIndex];

Expand All @@ -90,14 +92,14 @@ private void ConnectDefinition(string host, int port)
configRepo.VersionBuild = (byte)((IntVariable)_state.SymbolTable[PredefinedIdentifiers.VERSION].Identifiable).Value;

var connectionActions = c.Resolve<INetworkConnectionActions>();
var connectResult = connectionActions.ConnectToServer().GetAwaiter().GetResult();
var connectResult = await connectionActions.ConnectToServer();
if (connectResult != ConnectResult.Success)
throw new ArgumentException($"Bot {_botIndex}: Unable to connect to server! Host={host} Port={port}");

var backgroundReceiveActions = c.Resolve<IBackgroundReceiveActions>();
backgroundReceiveActions.RunBackgroundReceiveLoop();

var handshakeResult = connectionActions.BeginHandshake().GetAwaiter().GetResult();
var handshakeResult = await connectionActions.BeginHandshake();

if (handshakeResult.Response != InitReply.Success)
throw new InvalidOperationException($"Bot {_botIndex}: Invalid response from server or connection failed! Must receive an OK reply.");
Expand All @@ -112,7 +114,7 @@ private void ConnectDefinition(string host, int port)
connectionActions.CompleteHandshake(handshakeResult);
}

private void DisconnectDefinition()
private void Disconnect()
{
var c = DependencyMaster.TypeRegistry[_botIndex];
var backgroundReceiveActions = c.Resolve<IBackgroundReceiveActions>();
Expand All @@ -122,15 +124,45 @@ private void DisconnectDefinition()
connectionActions.DisconnectFromServer();
}

private int CreateAndLoginDefinition(string user, string pass)
private async Task<int> CreateAccountAsync(string user, string pass)
{
return (int)await _botHelper.CreateAccountAsync(user, pass);
}

private async Task<int> LoginAsync(string user, string pass)
{
var accountReply = _botHelper.CreateAccountAsync(user, pass).GetAwaiter().GetResult();
return (int)await _botHelper.LoginToAccountAsync(user, pass);
}

private async Task<int> CreateAndLoginAsync(string user, string pass)
{
var accountReply = (AccountReply)await CreateAccountAsync(user, pass);
if (accountReply == AccountReply.Created || accountReply == AccountReply.Exists)
{
return (int)_botHelper.LoginToAccountAsync(user, pass).GetAwaiter().GetResult();
return await LoginAsync(user, pass);
}

return (int)LoginReply.WrongUser;
}

private async Task<int> ChangePasswordAsync(string user, string oldPass, string newPass)
{
return (int)await _botHelper.ChangePasswordAsync(user, oldPass, newPass);
}

private async Task<int> CreateCharacterAsync(string charName)
{
return (int)await _botHelper.CreateCharacterAsync(charName);
}

private async Task<int> DeleteCharacterAsync(string charName, bool force)
{
return (int)await _botHelper.DeleteCharacterAsync(charName, force);
}

private Task LoginToCharacterAsync(string charName)
{
return _botHelper.LoginToCharacterAsync(charName);
}
}
}
7 changes: 4 additions & 3 deletions EOBot/Interpreter/States/AssignmentEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using EOBot.Interpreter.Variables;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace EOBot.Interpreter.States
{
Expand All @@ -13,11 +14,11 @@ public AssignmentEvaluator(IEnumerable<IScriptEvaluator> evaluators)
_evaluators = evaluators;
}

public bool Evaluate(ProgramState input)
public async Task<bool> EvaluateAsync(ProgramState input)
{
if (!_evaluators.OfType<VariableEvaluator>().Single().Evaluate(input) ||
if (!await _evaluators.OfType<VariableEvaluator>().Single().EvaluateAsync(input) ||
!input.Match(BotTokenType.AssignOperator) ||
!_evaluators.OfType<ExpressionEvaluator>().Single().Evaluate(input))
!await _evaluators.OfType<ExpressionEvaluator>().Single().EvaluateAsync(input))
{
return false;
}
Expand Down
19 changes: 10 additions & 9 deletions EOBot/Interpreter/States/BlockEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using EOBot.Interpreter.Variables;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace EOBot.Interpreter.States
{
Expand All @@ -14,15 +15,15 @@ protected BlockEvaluator(IEnumerable<IScriptEvaluator> evaluators)
_evaluators = evaluators;
}

public abstract bool Evaluate(ProgramState input);
public abstract Task<bool> EvaluateAsync(ProgramState input);

protected (bool, VariableBotToken) EvaluateCondition(int whileLoopStartIndex, ProgramState input)
protected async Task<(bool, VariableBotToken)> EvaluateConditionAsync(int blockStartIndex, ProgramState input)
{
input.Goto(whileLoopStartIndex);
input.Goto(blockStartIndex);

if (!input.Expect(BotTokenType.Keyword) ||
!input.Expect(BotTokenType.LParen) ||
!_evaluators.OfType<ExpressionEvaluator>().Single().Evaluate(input) ||
!await _evaluators.OfType<ExpressionEvaluator>().Single().EvaluateAsync(input) ||
!input.Expect(BotTokenType.RParen))
return (false, new VariableBotToken(BotTokenType.Error, string.Empty, UndefinedVariable.Instance));

Expand All @@ -32,7 +33,7 @@ protected BlockEvaluator(IEnumerable<IScriptEvaluator> evaluators)
return (true, (VariableBotToken)input.OperationStack.Pop());
}

protected bool EvaluateBlock(ProgramState input)
protected async Task<bool> EvaluateBlockAsync(ProgramState input)
{
input.Expect(BotTokenType.NewLine);

Expand All @@ -41,10 +42,10 @@ protected bool EvaluateBlock(ProgramState input)
// evaluated in separate blocks because we want to check statement list OR statement, not both
if (input.Expect(BotTokenType.LBrace))
{
if (!_evaluators.OfType<StatementListEvaluator>().Single().Evaluate(input))
if (!await _evaluators.OfType<StatementListEvaluator>().Single().EvaluateAsync(input))
return false;
}
else if (!_evaluators.OfType<StatementEvaluator>().Single().Evaluate(input))
else if (!await _evaluators.OfType<StatementEvaluator>().Single().EvaluateAsync(input))
{
return false;
}
Expand All @@ -61,7 +62,7 @@ protected void SkipBlock(ProgramState input)
// potential newline character - skip so we can advance execution beyond the block
input.Expect(BotTokenType.NewLine);

// skip the rest of the block (same as if statement)
// skip the rest of the block
if (input.Expect(BotTokenType.LBrace))
{
int rBraceCount = 1;
Expand All @@ -77,7 +78,7 @@ protected void SkipBlock(ProgramState input)
}
else
{
// optional newline after if
// optional newline after block
input.Expect(BotTokenType.NewLine);

while (input.Current().TokenType != BotTokenType.NewLine && input.Current().TokenType != BotTokenType.EOF)
Expand Down
15 changes: 8 additions & 7 deletions EOBot/Interpreter/States/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using EOBot.Interpreter.Variables;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace EOBot.Interpreter.States
{
Expand All @@ -13,19 +14,19 @@ public ExpressionEvaluator(IEnumerable<IScriptEvaluator> evaluators)
_evaluators = evaluators;
}

public bool Evaluate(ProgramState input)
public async Task<bool> EvaluateAsync(ProgramState input)
{
if (input.Expect(BotTokenType.LParen))
{
if (!_evaluators.OfType<ExpressionEvaluator>().Single().Evaluate(input))
if (!await _evaluators.OfType<ExpressionEvaluator>().Single().EvaluateAsync(input))
return false;

// if we get an RParen just be done
if (input.Expect(BotTokenType.RParen))
return true;

// expression_tail is optional
if (!_evaluators.OfType<ExpressionTailEvaluator>().Single().Evaluate(input))
if (!await _evaluators.OfType<ExpressionTailEvaluator>().Single().EvaluateAsync(input))
{
if (input.OperationStack.Count == 0)
return false;
Expand All @@ -43,20 +44,20 @@ public bool Evaluate(ProgramState input)
else
{
// an expression can be a function call
if (_evaluators.OfType<FunctionEvaluator>().Single().Evaluate(input))
if (await _evaluators.OfType<FunctionEvaluator>().Single().EvaluateAsync(input))
{
// there may or may not be an expression tail after a function call
if (!_evaluators.OfType<ExpressionTailEvaluator>().Single().Evaluate(input))
if (!await _evaluators.OfType<ExpressionTailEvaluator>().Single().EvaluateAsync(input))
return true;
}
else
{

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

// expression_tail is optional
if (!_evaluators.OfType<ExpressionTailEvaluator>().Single().Evaluate(input))
if (!await _evaluators.OfType<ExpressionTailEvaluator>().Single().EvaluateAsync(input))
{
if (input.OperationStack.Count == 0)
return false;
Expand Down
5 changes: 3 additions & 2 deletions EOBot/Interpreter/States/ExpressionTailEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using EOBot.Interpreter.Extensions;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace EOBot.Interpreter.States
{
Expand All @@ -13,7 +14,7 @@ public ExpressionTailEvaluator(IEnumerable<IScriptEvaluator> evaluators)
_evaluators = evaluators;
}

public bool Evaluate(ProgramState input)
public async Task<bool> EvaluateAsync(ProgramState input)
{
return input.MatchOneOf(
BotTokenType.EqualOperator,
Expand All @@ -26,7 +27,7 @@ public bool Evaluate(ProgramState input)
BotTokenType.MinusOperator,
BotTokenType.MultiplyOperator,
BotTokenType.DivideOperator)
&& _evaluators.OfType<ExpressionEvaluator>().Single().Evaluate(input);
&& await _evaluators.OfType<ExpressionEvaluator>().Single().EvaluateAsync(input);
}
}
}

0 comments on commit e729a0e

Please sign in to comment.