diff --git a/gwendolyn/ext/misc.py b/gwendolyn/ext/misc.py index 13b7d0b..31a6e66 100644 --- a/gwendolyn/ext/misc.py +++ b/gwendolyn/ext/misc.py @@ -3,21 +3,32 @@ from gwendolyn.utils import PARAMS as params class MiscExtension(Extension): """Contains the miscellaneous commands.""" + @slash_command(**params["misc"]["gen_name"]) + async def gen_name(self, ctx: SlashContext): + await self.bot.other.generate_name(ctx) + @slash_command(**params["misc"]["hello"]) async def hello(self, ctx: SlashContext): """Greet the bot.""" - await self.other.hello_func(ctx) + await self.bot.other.hello_func(ctx) @slash_command(**params["misc"]["help"]) async def help(self, ctx: SlashContext, command=""): """Get help for commands.""" await self.bot.other.help_func(ctx, command) - @slash_command(**params["misc"]["ping"]) async def ping(self, ctx: SlashContext): """Send the bot's latency.""" - await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms") + latency = self.bot.latency + if latency > 100: + await ctx.send("Cannot measure latency :(") + else: + await ctx.send(f"Pong!\nLatency is {round(self.bot.latency * 1000)}ms") + + @slash_command(**params["misc"]["roll"]) + async def roll(self, ctx: SlashContext, dice: str = "1d20"): + await self.bot.other.roll_dice(ctx, dice) @slash_command(**params["misc"]["thank"]) async def thank(self, ctx: SlashContext): diff --git a/gwendolyn/funcs/other/name_generator.py b/gwendolyn/funcs/other/name_generator.py new file mode 100644 index 0000000..2319e98 --- /dev/null +++ b/gwendolyn/funcs/other/name_generator.py @@ -0,0 +1,44 @@ +import random + +class NameGenerator(): + def __init__(self) -> None: + with open("gwendolyn/resources/names.txt", "r", encoding="UTF-8") as file_pointer: + self._names = file_pointer.read().lower().split("\n") + + self._gen_letter_dict() + + def _gen_letter_dict(self): + list_of_names = [] + for name in self._names: + if (name != ""): + list_of_names.append("__" + name + "__") + + dict_of_names = {} + for name in list_of_names: + for i in range(len(name)-3): + combination = name[i:i+2] + if combination not in dict_of_names: + dict_of_names[combination] = [] + dict_of_names[combination].append(name[i+2]) + + self._letter_dict = dict_of_names + + def generate(self): + combination = "__" + next_letter = "" + result = "" + + while True: + number_of_letters = len(self._letter_dict[combination]) + index = random.randint(0,number_of_letters - 1) + next_letter = self._letter_dict[combination][index] + if next_letter == "_": + break + else: + result = result + next_letter + combination = combination[1] + next_letter + + return result.title() + +if __name__ == "__main__": + print(NameGenerator().generate()) \ No newline at end of file diff --git a/gwendolyn/funcs/other/other.py b/gwendolyn/funcs/other/other.py index cef823c..d74a24d 100644 --- a/gwendolyn/funcs/other/other.py +++ b/gwendolyn/funcs/other/other.py @@ -2,12 +2,18 @@ import datetime # Used in hello_func from interactions import SlashContext, Embed, SlashCommand +from .name_generator import NameGenerator + +from .roll import DieRoller + def gen_help_text(commands: list[SlashCommand]): return '\n'.join([f"`/{i.name}`\t— {i.description}" for i in commands]) class Other(): def __init__(self, bot): self.bot = bot + self._name_generator = NameGenerator() + self._roller = DieRoller() # Responds with a greeting of a time-appropriate maner async def hello_func(self, ctx: SlashContext): @@ -48,3 +54,12 @@ class Other(): except: await ctx.send(f"Could not find a help file for the command '/{command}'") + async def generate_name(self, ctx: SlashContext): + await ctx.send(self._name_generator.generate()) + + async def roll_dice(self, ctx: SlashContext, dice: str): + try: + roll = self._roller.roll(dice) + await ctx.send(f":game_die:Rolling dice `{dice}`\n{roll}") + except: + await ctx.send("There was an error in the rolling") diff --git a/gwendolyn/funcs/other/roll/__init__.py b/gwendolyn/funcs/other/roll/__init__.py new file mode 100644 index 0000000..98b3f9e --- /dev/null +++ b/gwendolyn/funcs/other/roll/__init__.py @@ -0,0 +1 @@ +from .roll import DieRoller \ No newline at end of file diff --git a/gwendolyn/funcs/other/roll/ast_nodes.py b/gwendolyn/funcs/other/roll/ast_nodes.py new file mode 100644 index 0000000..2105602 --- /dev/null +++ b/gwendolyn/funcs/other/roll/ast_nodes.py @@ -0,0 +1,702 @@ +from __future__ import annotations +from random import randint + +from rply.token import BaseBox + +def show_exp(exp, vtable): + r = exp.show(vtable) + + if type(exp) not in [ExpInt,ExpRoll,ExpVar]: + r = f"({r})" + + return r + +class Exp(BaseBox): + def eval(self, vtable, *args): + return self._eval(vtable.copy(), *args) + + def show(self, vtable, *args): + return self._show(vtable.copy(), *args) + + def _eval(self, _): + return None + + def _show(self, _): + return "" + + def includes(self, _): + return False + + def __repr__(self) -> str: + return "exp()" + + def __eq__(self, other: Exp) -> bool: + return False + +class ExpInt(Exp): + def __init__(self, value: int): + self.value = value + + def _eval(self, _): + return self.value + + def _show(self, _): + return str(self.value) + + def includes(self, _): + return False + + def __repr__(self) -> str: + return f"exp_int({self.value})" + + def __eq__(self, other: Exp) -> bool: + return isinstance(other, ExpInt) and self.value == other.value + +class ExpMin(Exp): + def __init__(self, exp1: Exp, exp2: Exp): + self.exp1 = exp1 + self.exp2 = exp2 + + def _eval(self, vtable): + r1 = self.exp1.eval(vtable) + r2 = self.exp2.eval(vtable) + + if not (isinstance(r1,int) and isinstance(r2,int)): + return None + + return max(r1, r2) + + def _show(self, vtable): + r1 = show_exp(self.exp1, vtable) + r2 = show_exp(self.exp2, vtable) + + return f"{r1}min{r2}" + + def includes(self, var: str): + return self.exp1.includes(var) or self.exp2.includes(var) + + def __repr__(self) -> str: + return f"exp_min({self.exp1},{self.exp2})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpMin) and + self.exp1 == other.exp1 and + self.exp2 == other.exp2 + ) + +class ExpMax(Exp): + def __init__(self, exp1: Exp, exp2: Exp): + self.exp1 = exp1 + self.exp2 = exp2 + + def _eval(self, vtable): + r1 = self.exp1.eval(vtable) + r2 = self.exp2.eval(vtable) + + if not (isinstance(r1,int) and isinstance(r2,int)): + return None + + return min(r1, r2) + + def _show(self, vtable): + r1 = show_exp(self.exp1, vtable) + r2 = show_exp(self.exp2, vtable) + + return f"{r1}max{r2}" + + def includes(self, var: str): + return self.exp1.includes(var) or self.exp2.includes(var) + + def __repr__(self) -> str: + return f"exp_max({self.exp1},{self.exp2})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpMax) and + self.exp1 == other.exp1 and + self.exp2 == other.exp2 + ) + +class ExpBinop(Exp): + def __init__(self, op: str, exp1: Exp, exp2: Exp): + self.op = op + self.exp1 = exp1 + self.exp2 = exp2 + + def _eval(self, vtable): + r1 = self.exp1.eval(vtable) + r2 = self.exp2.eval(vtable) + + if not (isinstance(r1,int) and isinstance(r2,int)): + return None + + if self.op == "+": + return r1+r2 + elif self.op == "-": + return r1-r2 + elif self.op == "*": + return r1*r2 + elif self.op == "/": + return r1//r2 + else: + raise Exception(f"Unknown binop {self.op}") + + def _show(self, vtable): + r1 = show_exp(self.exp1, vtable) + r2 = show_exp(self.exp2, vtable) + + return f"{r1}{self.op}{r2}" + + def includes(self, var: str): + return self.exp1.includes(var) or self.exp2.includes(var) + + def __repr__(self) -> str: + return f"exp_binop({self.op}, {self.exp1}, {self.exp2})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpBinop) and + self.op == other.op and + self.exp1 == other.exp1 and + self.exp2 == other.exp2 + ) + +class ExpNeg(Exp): + def __init__(self, exp: Exp): + self.exp = exp + + def _eval(self, vtable): + r = self.exp.eval(vtable) + + if not isinstance(r,int): + return None + + return -r + + def _show(self, vtable): + r = show_exp(self.exp, vtable) + + return f"-{r}" + + def includes(self, var: str): + return self.exp.includes(var) + + def __repr__(self) -> str: + return f"exp_neg({self.exp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpNeg) and + self.exp == other.exp + ) + +class ExpLet(Exp): + def __init__(self, var: str, exp1: Exp, exp2: Exp): + self.var = var + self.exp1 = exp1 + self.exp2 = exp2 + + def _eval(self, vtable): + r1 = self.exp1.eval(vtable) + + vtable[self.var] = r1 + + return self.exp2.eval(vtable) + + def _show(self, vtable): + r1 = show_exp(self.exp1, vtable) + + vtable[self.var] = self.exp1.eval(vtable) + + r2 = show_exp(self.exp2, vtable) + + if self.exp2.includes(self.var): + return f"let {self.var}={r1} in {r2}" + else: + return r2 + + def includes(self, var: str): + return self.exp1.includes(var) or self.exp2.includes(var) + + def __repr__(self) -> str: + return f"exp_let({self.var}, {self.exp1}, {self.exp2})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpLet) and + self.var == other.var and + self.exp1 == other.exp1 and + self.exp2 == other.exp2 + ) + +class ExpIf(Exp): + def __init__(self, exp1: Exp, exp2: Exp, exp3: Exp): + self.exp1 = exp1 + self.exp2 = exp2 + self.exp3 = exp3 + + def _eval(self, vtable): + r1 = self.exp1.eval(vtable) + + if r1 > 0: + return self.exp2.eval(vtable) + else: + return self.exp3.eval(vtable) + + def _show(self, vtable): + r1 = show_exp(self.exp1, vtable) + r2 = show_exp(self.exp2, vtable) + r3 = self.exp3.show(vtable) + + return f"if {r1} then {r2} else {r3}" + + def includes(self, var: str): + return self.exp1.includes(var) or self.exp2.includes(var) or self.exp3.includes(var) + + def __repr__(self) -> str: + return f"exp_if({self.exp1}, {self.exp2}, {self.exp3})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpIf) and + self.exp1 == other.exp1 and + self.exp2 == other.exp2 and + self.exp3 == other.exp3 + ) + +class ExpLambda(Exp): + def __init__(self, var: str, exp: Exp): + self.var = var + self.exp = exp + + def _eval(self, _): + return (self.exp, self.var) + + def _show(self, vtable): + r = show_exp(self.exp, vtable) + + return f"\\{self.var} -> {r}" + + def includes(self, var: str): + return self.exp.includes(var) + + def __repr__(self) -> str: + return f"exp_lambda({self.var}, {self.exp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpLambda) and + self.var == other.var and + self.exp == other.exp + ) + +class ExpApply(Exp): + def __init__(self, exp1: Exp, exp2: Exp): + self.exp1 = exp1 + self.exp2 = exp2 + + def _eval(self, vtable): + r1 = self.exp1.eval(vtable) + if isinstance(r1, tuple): + r2 = self.exp2.eval(vtable) + vtable[r1[1]] = r2 + return r1[0].eval(vtable) + else: + return None + + def _show(self, vtable): + r1 = show_exp(self.exp1, vtable) + r2 = self.exp2.show(vtable) + return f"{r1}({r2})" + + def includes(self, var: str): + return self.exp1.includes(var) or self.exp2.includes(var) + + def __repr__(self) -> str: + return f"exp_apply({self.exp1}, {self.exp2})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpApply) and + self.exp1 == other.exp1 and + self.exp2 == other.exp2 + ) + +class ExpVar(Exp): + def __init__(self, var: str): + self.var = var + + def _eval(self, vtable): + return vtable[self.var] if self.var in vtable else None + + def _show(self, vtable): + return self.var + + def includes(self, var: str): + return var == self.var + + def __repr__(self) -> str: + return f"exp_var({self.var})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpVar) and + self.var == other.var + ) + +class ComparePoint(Exp): + def __init__(self, comp_op: str, exp: Exp) -> None: + self.comp_op = comp_op + self.exp = exp + + def _eval(self, vtable, val: int): + r = self.exp.eval(vtable) + + if not isinstance(r,int): + return None + if self.comp_op == "=": + return 1 if val == r else 0 + if self.comp_op == "<=": + return 1 if val <= r else 0 + else: + raise Exception(f"Unknown binop {self.op}") + + def _show(self, vtable): + r = show_exp(self.exp, vtable) + + return f"{self.comp_op}{r}" + + def includes(self, var: str): + return self.exp.includes(var) + + def __repr__(self) -> str: + return f"comp({self.comp_op},{self.exp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ComparePoint) and + self.comp_op == other.comp_op and + self.exp == other.exp + ) + +class ExpTest(Exp): + def __init__(self, exp: Exp, comp: ComparePoint) -> None: + self.exp = exp + self.comp = comp + + def _eval(self, vtable): + r = self.exp.eval(vtable) + + return self.comp.eval(vtable, r) + + def _show(self, vtable): + r = show_exp(self.exp, vtable) + c = self.comp.show(vtable) + + return f"{r}{c}" + + def includes(self, var: str): + return self.exp.includes(var) or self.comp.includes(var) + + def __repr__(self) -> str: + return f"test({self.exp},{self.comp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpTest) and + self.exp == other.exp and + self.comp == other.comp + ) + + +class ExpRoll(Exp): + def __init__(self, roll: Roll): + self.roll = roll + + def _eval(self, vtable): + return sum(self.roll.eval(vtable)) + + def _show(self, vtable): + return f"[{','.join(self.roll.show(vtable))}]" + + def includes(self, _): + return False + + def __repr__(self) -> str: + return f"sum({self.roll})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, ExpRoll) and + self.roll == other.roll + ) + +class Roll(Exp): + def __init__(self, exp1: Exp, exp2: Exp): + self.exp1 = exp1 + self.exp2 = exp2 + self.result = None + + def _eval(self, vtable): + if self.result is not None: + return self.result + + r1 = self.exp1.eval(vtable) + r2 = self.exp2.eval(vtable) + + if not (isinstance(r1,int) and isinstance(r2,int)): + return [] + + self.die_type = r2 + + self.result = [randint(1,r2) for _ in range(r1)] + return self.result + + def _show(self, vtable): + result = self.eval(vtable) + return [str(i) for i in result] + + @property + def die(self) -> int: + if hasattr(self, "die_type"): + return self.die_type + elif hasattr(self, "roll"): + return self.roll.die_type + else: + return 0 + + def __repr__(self) -> str: + return f"roll({self.exp1},{self.exp2})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, Roll) and + self.exp1 == other.exp1 and + self.exp2 == other.exp2 + ) + +class RollKeepHighest(Roll): + def __init__(self, roll: Roll, exp: Exp): + self.roll = roll + self.exp = exp + self.result = None + self.show_list = None + + def _eval(self, vtable): + if self.result is not None: + return self.result + + r1 = self.roll.eval(vtable) + r2 = self.exp.eval(vtable) + + if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)): + return [] + + max_indices = [] + for i, n in enumerate(r1): + if len(max_indices) < r2: + max_indices.append(i) + elif not all([n <= r1[j] for j in max_indices]): + max_indices.remove(min(max_indices,key=lambda x: r1[x])) + max_indices.append(i) + + self.result = [r1[i] for i in max_indices] + self.show_list = [str(n) if i in max_indices else f"~~{n}~~" for i, n in enumerate(r1)] + return self.result + + def _show(self, vtable): + self.eval(vtable) + return self.show_list + + def __repr__(self) -> str: + return f"kep_highest({self.roll},{self.exp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, RollKeepHighest) and + self.roll == other.roll and + self.exp == other.exp + ) + +class RollKeepLowest(Roll): + def __init__(self, roll: Roll, exp: Exp): + self.roll = roll + self.exp = exp + self.result = None + self.show_list = None + + def _eval(self, vtable): + if self.result is not None: + return self.result + + r1 = self.roll.eval(vtable) + r2 = self.exp.eval(vtable) + + if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)): + return [] + + min_indices = [] + for i, n in enumerate(r1): + if len(min_indices) < r2: + min_indices.append(i) + elif not all([n >= r1[j] for j in min_indices]): + min_indices.remove(max(min_indices,key=lambda x: r1[x])) + min_indices.append(i) + + self.result = [r1[i] for i in min_indices] + self.show_list = [str(n) if i in min_indices else f"~~{n}~~" for i, n in enumerate(r1)] + return self.result + + def _show(self, vtable): + self.eval(vtable) + return self.show_list + + def __repr__(self) -> str: + return f"kep_lowest({self.roll},{self.exp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, RollKeepLowest) and + self.roll == other.roll and + self.exp == other.exp + ) + +class RollMin(Roll): + def __init__(self, roll: Roll, exp: Exp): + self.roll = roll + self.exp = exp + self.result = None + self.show_list = None + + def _eval(self, vtable): + if self.result is not None: + return self.result + + r1 = self.roll.eval(vtable) + r2 = self.exp.eval(vtable) + + if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)): + return [] + + self.show_list = [] + + for i in range(len(r1)): + if r1[i] < r2: + r1[i] = r2 + self.show_list.append(f"{r2}^") + else: + self.show_list.append(str(r1[i])) + + self.result = r1 + return self.result + + def _show(self, vtable): + self.eval(vtable) + return self.show_list + + def __repr__(self) -> str: + return f"min({self.roll},{self.exp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, RollMin) and + self.roll == other.roll and + self.exp == other.exp + ) + +class RollMax(Roll): + def __init__(self, roll: Roll, exp: Exp): + self.roll = roll + self.exp = exp + self.result = None + self.show_list = None + + def _eval(self, vtable): + if self.result is not None: + return self.result + + r1 = self.roll.eval(vtable) + r2 = self.exp.eval(vtable) + + if not ((isinstance(r1,list) and all(isinstance(i,int) for i in r1)) and isinstance(r2,int)): + return [] + + self.show_list = [] + + for i in range(len(r1)): + if r1[i] > r2: + r1[i] = r2 + self.show_list.append(f"{r2}v") + else: + self.show_list.append(str(r1[i])) + + self.result = r1 + return self.result + + def _show(self, vtable): + self.eval(vtable) + return self.show_list + + def __repr__(self) -> str: + return f"max({self.roll},{self.exp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, RollMax) and + self.roll == other.roll and + self.exp == other.exp + ) + +class RollExplode(Roll): + def __init__(self, roll: Roll, comp: ComparePoint = None): + self.roll = roll + self.comp = comp + self.result = None + self.show_list = None + + def _eval(self, vtable): + if self.result is not None: + return self.result + + r1 = self.roll.eval(vtable) + + if not (isinstance(r1,list) and all(isinstance(i,int) for i in r1)): + return [] + + + d = self.die + + if self.comp is None: + self.comp = ComparePoint("=", ExpInt(d)) + + self.result = [] + self.show_list = [] + + def compare(n): + if self.comp.eval(vtable, n): + self.result.append(n) + self.show_list.append(f"{n}!") + compare(randint(1,d)) + else: + self.result.append(n) + self.show_list.append(str(n)) + + for n in r1: + compare(n) + + return self.result + + def _show(self, vtable): + self.eval(vtable) + return self.show_list + + def __repr__(self) -> str: + return f"max({self.roll},{self.exp})" + + def __eq__(self, other: Exp) -> bool: + return ( + isinstance(other, RollMax) and + self.roll == other.roll and + self.exp == other.exp + ) \ No newline at end of file diff --git a/gwendolyn/funcs/other/roll/lexer.py b/gwendolyn/funcs/other/roll/lexer.py new file mode 100644 index 0000000..46a70a5 --- /dev/null +++ b/gwendolyn/funcs/other/roll/lexer.py @@ -0,0 +1,48 @@ + +from string import ascii_letters, digits +from rply import LexerGenerator + +VALID_CHARACTERS = ascii_letters[:3]+ascii_letters[4:]+"_"+digits + +TOKENS = [ + ("ROLL_DIE", r"d"), + ("ROLL_KEEP_HIGHEST", r"kh"), + ("ROLL_KEEP_LOWEST", r"kl"), + ("ROLL_MIN", r"min"), + ("ROLL_MAX", r"max"), + ("ROLL_EXPLODE", r"!"), + + ("SYMBOL_LE", r"\<\="), + # ("SYMBOL_GE", r"\>\="), + ("SYMBOL_EQUALS", r"\="), + ("SYMBOL_ARROW", r"\-\>"), + ("SYMBOL_BACKSLASH", r"\\"), + ("SYMBOL_LPARENS", r"\("), + ("SYMBOL_RPARENS", r"\)"), + ("SYMBOL_PLUS", r"\+"), + ("SYMBOL_MINUS", r"\-"), + ("SYMBOL_TIMES", r"\*"), + ("SYMBOL_DIVIDE", r"\/"), + + ("KEYWORD_LET", r"let"), + ("KEYWORD_IN", r"in"), + ("KEYWORD_IF", r"if"), + ("KEYWORD_THEN", r"then"), + ("KEYWORD_ELSE", r"else"), + + ("INT", r"\d+"), + ("ID", f"[{ascii_letters}][{VALID_CHARACTERS}]*") +] + +class Lexer(): + def __init__(self): + self.lexer = LexerGenerator() + + def _add_tokens(self): + for token in TOKENS: + self.lexer.add(*token) + self.lexer.ignore(r"\s+") + + def get_lexer(self): + self._add_tokens() + return self.lexer.build() diff --git a/gwendolyn/funcs/other/roll/parser.py b/gwendolyn/funcs/other/roll/parser.py new file mode 100644 index 0000000..f02df26 --- /dev/null +++ b/gwendolyn/funcs/other/roll/parser.py @@ -0,0 +1,134 @@ +from rply import ParserGenerator + +from lexer import TOKENS +from ast_nodes import Exp, ExpInt, ExpBinop, ExpLet, ExpVar, ExpMin, ExpMax, ExpRoll, Roll, RollKeepHighest, RollKeepLowest, RollMin, RollMax, RollExplode, ComparePoint, ExpIf, ExpTest, ExpApply, ExpLambda, ExpNeg + + +class Parser(): + def __init__(self): + self.pg = ParserGenerator( + [i[0] for i in TOKENS], + precedence=[ + ('left', ["SYMBOL_BACKSLASH","SYMBOL_ARROW"]), + ('left', ["KEYWORD_LET", "KEYWORD_IN", "KEYWORD_IF", "KEYWORD_THEN", "KEYWORD_ELSE"]), + ('left', ["SYMBOL_EQUALS", "SYMBOL_LE"]), + ('left', ["SYMBOL_PLUS", "SYMBOL_MINUS"]), + ('left', ["SYMBOL_TIMES", "SYMBOL_DIVIDE"]), + ('left', ["ROLL_DIE"]), + ('right', ["ROLL_KEEP_HIGHEST","ROLL_KEEP_LOWEST","ROLL_MIN","ROLL_MAX","ROLL_EXPLODE"]) + ] + ) + self._get_parser() + + def parse(self, token_input): + return self.parser.parse(token_input) + + def _get_parser(self) -> Exp: + # Expressions + @self.pg.production('exp : exp SYMBOL_PLUS exp') + @self.pg.production('exp : exp SYMBOL_MINUS exp') + @self.pg.production('exp : exp SYMBOL_TIMES exp') + @self.pg.production('exp : exp SYMBOL_DIVIDE exp') + def exp_mul_div(tokens): + return ExpBinop(tokens[1].value, tokens[0], tokens[2]) + + @self.pg.production('exp : atom') + def exp_atom(tokens): + return tokens[0] + + @self.pg.production("exp : SYMBOL_MINUS atom") + def exp_neg(tokens): + return ExpNeg(tokens[1]) + + @self.pg.production('exp : KEYWORD_LET ID SYMBOL_EQUALS exp KEYWORD_IN exp') + def exp_let(tokens): + return ExpLet(tokens[1].value, tokens[3], tokens[5]) + + @self.pg.production('exp : roll') + def exp_roll(tokens): + return ExpRoll(tokens[0]) + + @self.pg.production("exp : atom ROLL_MIN atom") + def exp_min(tokens): + return ExpMin(tokens[0], tokens[2]) + + @self.pg.production("exp : atom ROLL_MAX atom") + def exp_max(tokens): + return ExpMax(tokens[0], tokens[2]) + + @self.pg.production("exp : atom comp") + def exp_test(tokens): + return ExpTest(tokens[0], tokens[1]) + + @self.pg.production("exp : KEYWORD_IF exp KEYWORD_THEN exp KEYWORD_ELSE exp") + def exp_if(tokens): + return ExpIf(tokens[1],tokens[3],tokens[5]) + + @self.pg.production("exp : SYMBOL_BACKSLASH ID SYMBOL_ARROW exp") + def exp_lamda(tokens): + return ExpLambda(tokens[1].value, tokens[3]) + + @self.pg.production("exp : atom atom") + def apply(tokens): + return ExpApply(tokens[0],tokens[1]) + + # Rolls + @self.pg.production('roll : roll ROLL_KEEP_HIGHEST atom') + def roll_keep_highest(tokens): + return RollKeepHighest(tokens[0], tokens[2]) + + @self.pg.production('roll : roll ROLL_KEEP_LOWEST atom') + def roll_keep_lowest(tokens): + return RollKeepLowest(tokens[0], tokens[2]) + + @self.pg.production('roll : roll ROLL_MIN atom') + def roll_min(tokens): + return RollMin(tokens[0], tokens[2]) + + @self.pg.production('roll : roll ROLL_MAX atom') + def roll_max(tokens): + return RollMax(tokens[0], tokens[2]) + + @self.pg.production('roll : roll ROLL_EXPLODE ') + def roll_explode(tokens): + return RollExplode(tokens[0]) + + @self.pg.production('roll : roll ROLL_EXPLODE comp') + def roll_explode_comp(tokens): + return RollExplode(tokens[0], tokens[2]) + + @self.pg.production('roll : atom ROLL_DIE atom') + def roll(tokens): + return Roll(tokens[0], tokens[2]) + + @self.pg.production('roll : ROLL_DIE atom') + def roll_no_amount(tokens): + return Roll(ExpInt(1), tokens[1]) + + # Compare Points + + @self.pg.production("comp : SYMBOL_EQUALS atom") + @self.pg.production("comp : SYMBOL_LE atom") + def comp_point(tokens): + return ComparePoint(tokens[0].value,tokens[1]) + + # Atoms + @self.pg.production("atom : INT") + def atom_int(tokens): + return ExpInt(int(tokens[0].value)) + + @self.pg.production("atom : SYMBOL_LPARENS exp SYMBOL_RPARENS") + def atom_exp(tokens): + return tokens[1] + + @self.pg.production('atom : ID') + def atom_var(tokens): + return ExpVar(tokens[0].value) + + ## Error Handling ## + @self.pg.error + def error_handle(token): + raise Exception(f"Unexpected token '{token.value}' ({token.name}) at line {token.source_pos.lineno}, column {token.source_pos.colno}.") + + ## Finish ## + self.parser = self.pg.build() \ No newline at end of file diff --git a/gwendolyn/funcs/other/roll/roll.py b/gwendolyn/funcs/other/roll/roll.py new file mode 100644 index 0000000..cfea22d --- /dev/null +++ b/gwendolyn/funcs/other/roll/roll.py @@ -0,0 +1,58 @@ +import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__))) + +from lexer import Lexer +from parser import Parser +from ast_nodes import Exp, ExpInt, ExpBinop, ExpLet, ExpVar, ExpRoll, Roll, RollKeepHighest, RollKeepLowest, RollMin, RollMax, ExpMin, ExpMax + + +class DieRoller(): + def __init__(self) -> None: + self.lexer = Lexer().get_lexer() + self.parser = Parser() + + def parse(self, string: str) -> Exp: + tokens = self.lexer.lex(string) + expression = self.parser.parse(tokens) + return expression + + def roll(self, string: str): + exp = self.parse(string) + # print(exp) + # exit() + show = exp.show({}) + eval = exp.eval({}) + if show is None or eval is None or isinstance(eval,tuple): + raise Exception("Something went wrong") + return f"**Result**: {show}\n**Total**: {eval}" + +if __name__ == "__main__": + d = DieRoller() + test_strings = [ + ("4", ExpInt(4)), + ("1 + 1", ExpBinop("+",ExpInt(1),ExpInt(1))), + ("5*5", ExpBinop("*",ExpInt(5),ExpInt(5))), + ("1+5/5", ExpBinop("+",ExpInt(1),ExpBinop("/",ExpInt(5),ExpInt(5)))), + ("(1+5)/6", ExpBinop("/",ExpBinop("+",ExpInt(1),ExpInt(5)),ExpInt(6))), + ("let x=5 in x*2", ExpLet("x",ExpInt(5),ExpBinop("*",ExpVar("x"),ExpInt(2)))), + ("let x= let y = 2 in y in let z = 5 in x*z", ExpLet("x",ExpLet("y",ExpInt(2),ExpVar("y")),ExpLet("z",ExpInt(5),ExpBinop("*",ExpVar("x"),ExpVar("z"))))), + ("4d6", ExpRoll(Roll(ExpInt(4),ExpInt(6)))), + ("d6", ExpRoll(Roll(ExpInt(1),ExpInt(6)))), + ("(2+2)d(3*2)", ExpRoll(Roll(ExpBinop("+",ExpInt(2),ExpInt(2)),ExpBinop("*",ExpInt(3),ExpInt(2))))), + ("4d3*2", ExpBinop("*",ExpRoll(Roll(ExpInt(4),ExpInt(3))),ExpInt(2))), + ("4d6kh3", ExpRoll(RollKeepHighest(Roll(ExpInt(4),ExpInt(6)),ExpInt(3)))), + ("2d20kl1", ExpRoll(RollKeepLowest(Roll(ExpInt(2),ExpInt(20)),ExpInt(1)))), + ("11d10kh6kl1", ExpRoll(RollKeepLowest(RollKeepHighest(Roll(ExpInt(11),ExpInt(10)),ExpInt(6)),ExpInt(1)))), + ("3d6min6", ExpRoll(RollMin(Roll(ExpInt(3),ExpInt(6)),ExpInt(6)))), + ("3d6max1", ExpRoll(RollMax(Roll(ExpInt(3),ExpInt(6)),ExpInt(1)))), + ("let x = 1d4 in (x)min3", ExpLet("x",ExpRoll(Roll(ExpInt(1),ExpInt(4))),ExpMin(ExpVar("x"),ExpInt(3)))), + ("10max(2d10)",ExpMax(ExpInt(10),ExpRoll(Roll(ExpInt(2),ExpInt(10))))) + ] + for s, t in test_strings: + r = d.parse(s) + correct = r == t + print(f"{str(s) : <20}: {'✅' if correct else '❌'} ({r.eval({})})") + if not correct: + print(f"Expected: {t}") + print(f"Actual: {r}\n") + + print(d.roll(input(":"))) \ No newline at end of file diff --git a/gwendolyn/resources/slash_parameters.json b/gwendolyn/resources/slash_parameters.json index 22c687d..5f0082e 100644 --- a/gwendolyn/resources/slash_parameters.json +++ b/gwendolyn/resources/slash_parameters.json @@ -16,10 +16,26 @@ } ] }, + "gen_name" : { + "name": "gen_name", + "description": "Generate a random name" + }, "ping" : { "name" : "ping", "description" : "Get the Gwendolyn's latency to the server" }, + "roll" : { + "name": "roll", + "description": "Roll dice", + "options": [ + { + "name": "dice", + "description": "The dice to be rolled", + "type": 3, + "required": "false" + } + ] + }, "stop" : { "name" : "stop", "description" : "Stop Gwendolyn"