This commit is contained in:
Nikolaj
2024-10-30 15:31:46 +01:00
parent e955ef4e28
commit 2f4e606fbf
9 changed files with 1032 additions and 3 deletions

View File

@ -3,22 +3,33 @@ 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."""
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):
"""Thank the bot."""

View File

@ -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())

View File

@ -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")

View File

@ -0,0 +1 @@
from .roll import DieRoller

View File

@ -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
)

View File

@ -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()

View File

@ -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()

View File

@ -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(":")))

View File

@ -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"