Skip to content

Commit

Permalink
Merge pull request #103 from ethanmoffat/bot_script_objects
Browse files Browse the repository at this point in the history
EOBot: Add support for objects in script files. Define $account, $character, and $mapstate built-in variables.
  • Loading branch information
ethanmoffat committed Feb 4, 2022
2 parents c0ca597 + 9b138a4 commit fc07b10
Show file tree
Hide file tree
Showing 18 changed files with 387 additions and 69 deletions.
2 changes: 2 additions & 0 deletions EOBot/EOBot.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<Compile Include="Interpreter\BotTokenParser.cs" />
<Compile Include="Interpreter\BotTokenType.cs" />
<Compile Include="Interpreter\BuiltInIdentifierConfigurator.cs" />
<Compile Include="Interpreter\Extensions\SymbolTableExtensions.cs" />
<Compile Include="Interpreter\Extensions\ProgramStateExtensions.cs" />
<Compile Include="Interpreter\IdentifierBotToken.cs" />
<Compile Include="Interpreter\States\AssignmentEvaluator.cs" />
Expand All @@ -124,6 +125,7 @@
<Compile Include="Interpreter\Variables\BoolVariable.cs" />
<Compile Include="Interpreter\VariableBotToken.cs" />
<Compile Include="Interpreter\Variables\AsyncFunction.cs" />
<Compile Include="Interpreter\Variables\ObjectVariable.cs" />
<Compile Include="Interpreter\Variables\PredefinedIdentifiers.cs" />
<Compile Include="Interpreter\Variables\UndefinedVariable.cs" />
<Compile Include="Interpreter\Variables\AsyncVoidFunction.cs" />
Expand Down
13 changes: 9 additions & 4 deletions EOBot/Interpreter/BotTokenParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public BotToken GetNextToken()
do
{
inputChar = Read();
} while (inputChar != '\n');
} while (inputChar != '\n' && !_inputStream.EndOfStream);

LineNumber++;
Column = 1;
Expand All @@ -86,7 +86,7 @@ public BotToken GetNextToken()
if (char.IsLetter(inputChar))
{
var identifier = inputChar.ToString();
while (char.IsLetterOrDigit(Peek()) || Peek() == '_')
while ((char.IsLetterOrDigit(Peek()) || Peek() == '_') && !_inputStream.EndOfStream)
identifier += Read();

var type = Keywords.Contains(identifier)
Expand All @@ -98,7 +98,7 @@ public BotToken GetNextToken()
else if (char.IsDigit(inputChar))
{
var number = inputChar.ToString();
while (char.IsDigit((char)_inputStream.Peek()))
while (char.IsDigit((char)_inputStream.Peek()) && !_inputStream.EndOfStream)
number += Read();
return Token(BotTokenType.Literal, number);
}
Expand All @@ -117,8 +117,12 @@ public BotToken GetNextToken()
case '"':
{
var stringLiteral = string.Empty;
while ((char)_inputStream.Peek() != '"')
while ((char)_inputStream.Peek() != '"' && !_inputStream.EndOfStream)
stringLiteral += Read();

if (_inputStream.EndOfStream)
return Token(BotTokenType.Error, string.Empty);

Read();
return Token(BotTokenType.Literal, stringLiteral);
}
Expand Down Expand Up @@ -184,6 +188,7 @@ public BotToken GetNextToken()
case '-': return Token(BotTokenType.MinusOperator, inputChar.ToString());
case '*': return Token(BotTokenType.MultiplyOperator, inputChar.ToString());
case '/': return Token(BotTokenType.DivideOperator, inputChar.ToString());
case '.': return Token(BotTokenType.Dot, inputChar.ToString());
default: return Token(BotTokenType.Error, inputChar.ToString());
}
}
Expand Down
1 change: 1 addition & 0 deletions EOBot/Interpreter/BotTokenType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum BotTokenType
MinusOperator,
MultiplyOperator,
DivideOperator,
Dot,
NewLine,
Error,
}
Expand Down
146 changes: 135 additions & 11 deletions EOBot/Interpreter/BuiltInIdentifierConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@
using EOBot.Interpreter.Variables;
using EOLib.Config;
using EOLib.Domain.Account;
using EOLib.Domain.Character;
using EOLib.Domain.Login;
using EOLib.Domain.Map;
using EOLib.Domain.NPC;
using EOLib.Domain.Protocol;
using EOLib.IO.Repositories;
using EOLib.Net.Communication;
using EOLib.Net.Connection;
using EOLib.Net.PacketProcessing;
Expand Down Expand Up @@ -35,8 +39,9 @@ public void SetupBuiltInFunctions()
_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)));
_state.SymbolTable[PredefinedIdentifiers.TIME] = Readonly(new Function<string>(PredefinedIdentifiers.TIME, () => DateTime.Now.ToLongTimeString()));
_state.SymbolTable[PredefinedIdentifiers.SLEEP_FUNC] = Readonly(new VoidFunction<int>(PredefinedIdentifiers.SLEEP_FUNC, param1 => Thread.Sleep(param1)));
_state.SymbolTable[PredefinedIdentifiers.TIME_FUNC] = Readonly(new Function<string>(PredefinedIdentifiers.TIME_FUNC, () => DateTime.Now.ToLongTimeString()));
_state.SymbolTable[PredefinedIdentifiers.OBJECT_FUNC] = Readonly(new Function<ObjectVariable>(PredefinedIdentifiers.OBJECT_FUNC, () => new ObjectVariable()));

BotDependencySetup();
_state.SymbolTable[PredefinedIdentifiers.CONNECT_FUNC] = Readonly(new AsyncVoidFunction<string, int>(PredefinedIdentifiers.CONNECT_FUNC, ConnectAsync));
Expand All @@ -49,12 +54,6 @@ public void SetupBuiltInFunctions()
_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()
{
_state.SymbolTable[PredefinedIdentifiers.HOST] = (true, new StringVariable(_parsedArgs.Host));
Expand All @@ -69,9 +68,14 @@ public void SetupBuiltInVariables()
_state.SymbolTable[PredefinedIdentifiers.VERSION] = (false, new IntVariable(28));

_state.SymbolTable[PredefinedIdentifiers.RESULT] = (false, UndefinedVariable.Instance);
_state.SymbolTable[PredefinedIdentifiers.ACCOUNT] = (true, UndefinedVariable.Instance);
_state.SymbolTable[PredefinedIdentifiers.CHARACTER] = (true, UndefinedVariable.Instance);
_state.SymbolTable[PredefinedIdentifiers.MAPSTATE] = (true, UndefinedVariable.Instance);
_state.SymbolTable[PredefinedIdentifiers.ACCOUNT] = SetupAccountObject();
_state.SymbolTable[PredefinedIdentifiers.CHARACTER] = SetupCharacterObject();
_state.SymbolTable[PredefinedIdentifiers.MAPSTATE] = SetupMapStateObject();
}

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

private void BotDependencySetup()
Expand Down Expand Up @@ -167,5 +171,125 @@ private Task LoginToCharacterAsync(string charName)
{
return _botHelper.LoginToCharacterAsync(charName);
}

private (bool, IIdentifiable) SetupAccountObject()
{
var playerInfoProv = DependencyMaster.TypeRegistry[_botIndex].Resolve<IPlayerInfoProvider>();
var charSelectProv = DependencyMaster.TypeRegistry[_botIndex].Resolve<ICharacterSelectorProvider>();

var accountObj = new RuntimeEvaluatedMemberObjectVariable();
accountObj.SymbolTable[PredefinedIdentifiers.NAME] = (true, () => new StringVariable(playerInfoProv.LoggedInAccountName));
accountObj.SymbolTable[PredefinedIdentifiers.CHARACTERS] = (true,
() => new ArrayVariable(
charSelectProv.Characters.Select(x =>
{
var retObj = new ObjectVariable();
retObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(x.Name));
return (IVariable)retObj;
}).ToList()));

return Readonly(accountObj);
}

private (bool, IIdentifiable) SetupCharacterObject()
{
var cp = DependencyMaster.TypeRegistry[_botIndex].Resolve<ICharacterProvider>();
var inventoryProvider = DependencyMaster.TypeRegistry[_botIndex].Resolve<ICharacterInventoryProvider>();
var pubProvider = DependencyMaster.TypeRegistry[_botIndex].Resolve<IPubFileProvider>();

var charObj = new RuntimeEvaluatedMemberObjectVariable();
charObj.SymbolTable[PredefinedIdentifiers.NAME] = (true, () => new StringVariable(cp.MainCharacter.Name));
charObj.SymbolTable["map"] = (true, () => new IntVariable(cp.MainCharacter.MapID));
charObj.SymbolTable["x"] = (true, () => new IntVariable(cp.MainCharacter.RenderProperties.MapX));
charObj.SymbolTable["y"] = (true, () => new IntVariable(cp.MainCharacter.RenderProperties.MapY));
charObj.SymbolTable["direction"] = (true, () => new IntVariable((int)cp.MainCharacter.RenderProperties.Direction));
charObj.SymbolTable["inventory"] = (true,
() => new ArrayVariable(
inventoryProvider.ItemInventory.Select(x =>
{
var itemName = pubProvider.EIFFile.Data.Single(d => d.ID == x.ItemID).Name;
var retObj = new ObjectVariable();
retObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(itemName));
retObj.SymbolTable["id"] = Readonly(new IntVariable(x.ItemID));
retObj.SymbolTable["amount"] = Readonly(new IntVariable(x.Amount));
return (IVariable)retObj;
}).ToList()));
charObj.SymbolTable["spells"] = (true,
() => new ArrayVariable(
inventoryProvider.SpellInventory.Select(x =>
{
var spellName = pubProvider.ESFFile.Data.Single(d => d.ID == x.ID).Name;
var retObj = new ObjectVariable();
retObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(spellName));
retObj.SymbolTable["id"] = Readonly(new IntVariable(x.ID));
retObj.SymbolTable["amount"] = Readonly(new IntVariable(x.Level));
return (IVariable)retObj;
}).ToList()));
charObj.SymbolTable["stats"] = (true,
() =>
{
var statsObj = new ObjectVariable();
statsObj.SymbolTable["hp"] = Readonly(new IntVariable(cp.MainCharacter.Stats[CharacterStat.HP]));
statsObj.SymbolTable["maxhp"] = Readonly(new IntVariable(cp.MainCharacter.Stats[CharacterStat.MaxHP]));
statsObj.SymbolTable["weight"] = Readonly(new IntVariable(cp.MainCharacter.Stats[CharacterStat.Weight]));
statsObj.SymbolTable["maxweight"] = Readonly(new IntVariable(cp.MainCharacter.Stats[CharacterStat.MaxWeight]));
statsObj.SymbolTable["tp"] = Readonly(new IntVariable(cp.MainCharacter.Stats[CharacterStat.TP]));
statsObj.SymbolTable["maxtp"] = Readonly(new IntVariable(cp.MainCharacter.Stats[CharacterStat.MaxTP]));
return statsObj;
});

return Readonly(charObj);
}

private (bool, IIdentifiable) SetupMapStateObject()
{
var ms = DependencyMaster.TypeRegistry[_botIndex].Resolve<ICurrentMapStateProvider>();

var mapStateObj = new RuntimeEvaluatedMemberObjectVariable();
mapStateObj.SymbolTable["characters"] = (true, () => new ArrayVariable(ms.Characters.Select(GetMapStateCharacter).ToList()));
mapStateObj.SymbolTable["npcs"] = (true, () => new ArrayVariable(ms.NPCs.Select(GetMapStateNPC).ToList()));
mapStateObj.SymbolTable["items"] = (true, () => new ArrayVariable(ms.MapItems.Select(GetMapStateItem).ToList()));

return Readonly(mapStateObj);
}

private IVariable GetMapStateCharacter(ICharacter c)
{
var charObj = new ObjectVariable();
charObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(c.Name));
charObj.SymbolTable["map"] = Readonly(new IntVariable(c.MapID));
charObj.SymbolTable["x"] = Readonly(new IntVariable(c.RenderProperties.MapX));
charObj.SymbolTable["y"] = Readonly(new IntVariable(c.RenderProperties.MapY));
charObj.SymbolTable["direction"] = Readonly(new IntVariable((int)c.RenderProperties.Direction));
return charObj;
}

private IVariable GetMapStateNPC(INPC npc)
{
var npcFile = DependencyMaster.TypeRegistry[_botIndex].Resolve<IPubFileProvider>().ENFFile;

var npcObj = new ObjectVariable();
npcObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(npcFile.Data.Single(x => x.ID == npc.ID).Name));
npcObj.SymbolTable["x"] = Readonly(new IntVariable(npc.X));
npcObj.SymbolTable["y"] = Readonly(new IntVariable(npc.Y));
npcObj.SymbolTable["id"] = Readonly(new IntVariable(npc.ID));
npcObj.SymbolTable["direction"] = Readonly(new IntVariable((int)npc.Direction));
return npcObj;
}

private IVariable GetMapStateItem(IItem item)
{
var itemFile = DependencyMaster.TypeRegistry[_botIndex].Resolve<IPubFileProvider>().EIFFile;

var itemObj = new ObjectVariable();
itemObj.SymbolTable[PredefinedIdentifiers.NAME] = Readonly(new StringVariable(itemFile.Data.Single(x => x.ID == item.ItemID).Name));
itemObj.SymbolTable["x"] = Readonly(new IntVariable(item.X));
itemObj.SymbolTable["y"] = Readonly(new IntVariable(item.Y));
itemObj.SymbolTable["id"] = Readonly(new IntVariable(item.ItemID));
itemObj.SymbolTable["amount"] = Readonly(new IntVariable(item.Amount));
return itemObj;
}
}
}
56 changes: 56 additions & 0 deletions EOBot/Interpreter/Extensions/SymbolTableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using EOBot.Interpreter.States;
using EOBot.Interpreter.Variables;
using System.Collections.Generic;

namespace EOBot.Interpreter.Extensions
{
public static class SymbolTableExtensions
{

public static (EvalResult Result, string Reason, IVariable Variable) GetVariable(this Dictionary<string, (bool, IIdentifiable Identifiable)> symbols, string identifier, int? arrayIndex = null)
{
if (!symbols.ContainsKey(identifier))
symbols[identifier] = (false, UndefinedVariable.Instance);

var variableValue = symbols[identifier].Identifiable as IVariable;
if (variableValue == null)
{
return (EvalResult.Failed, $"Identifier {identifier} is not a variable", null);
}

if (arrayIndex != null)
{
var arrayVariable = variableValue as ArrayVariable;

if (arrayVariable == null)
{
return (EvalResult.Failed, $"Identifier {identifier} is not an array", null);
}

if (arrayVariable.Value.Count <= arrayIndex.Value)
{
return (EvalResult.Failed, $"Index {arrayIndex.Value} is out of range of the array {identifier} (size {arrayVariable.Value.Count})", null);
}

variableValue = arrayVariable.Value[arrayIndex.Value];
}

return (EvalResult.Ok, string.Empty, variableValue);
}

public static (EvalResult Result, string Reason, T Variable) GetVariable<T>(this Dictionary<string, (bool, IIdentifiable)> symbols, string identifier, int? arrayIndex = null)
where T : class, IVariable
{
var getResult = GetVariable(symbols, identifier, arrayIndex);

if (getResult.Result != EvalResult.Ok)
return (getResult.Result, getResult.Reason, null);

var variable = getResult.Variable as T;
if (variable == null)
return (EvalResult.Failed, $"Identifier {identifier} is not a {typeof(T).Name}", null);

return (EvalResult.Ok, string.Empty, variable);
}
}
}
7 changes: 5 additions & 2 deletions EOBot/Interpreter/IdentifierBotToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ public class IdentifierBotToken : BotToken
{
public int? ArrayIndex { get; }

public IdentifierBotToken(BotTokenType tokenType, string tokenValue, int lineNumber, int column, int? arrayIndex = null)
: base(tokenType, tokenValue, lineNumber, column)
public IdentifierBotToken Member { get; }

public IdentifierBotToken(BotToken identifier, int? arrayIndex = null, IdentifierBotToken member = null)
: base(identifier.TokenType, identifier.TokenValue, identifier.LineNumber, identifier.Column)
{
ArrayIndex = arrayIndex;
Member = member;
}
}
}

0 comments on commit fc07b10

Please sign in to comment.