From 9187528a1e4d96aee5381138547c62ed7e281aa8 Mon Sep 17 00:00:00 2001 From: romangraef Date: Tue, 15 May 2018 20:02:18 +0200 Subject: ~stream language --- .gitignore | 8 ++++ gen_antlr.sh | 5 +++ guild_cog.py | 5 +++ launch.sh | 7 ++++ main.py | 13 +++++-- query/Query.g4 | 33 ++++++++++++++++ query/__init__.py | 1 + query/parse.py | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + stream_cog.py | 26 +++++++++++++ 10 files changed, 208 insertions(+), 4 deletions(-) create mode 100644 gen_antlr.sh create mode 100644 guild_cog.py create mode 100755 launch.sh create mode 100644 query/Query.g4 create mode 100644 query/__init__.py create mode 100644 query/parse.py create mode 100644 stream_cog.py diff --git a/.gitignore b/.gitignore index 2365604..7a934f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,10 @@ token.txt venv/ + +query/Query.tokens +query/QueryLexer.py +query/QueryLexer.tokens +query/QueryListener.py +query/QueryParser.py +query/debug.txt +query/QueryVisitor.py diff --git a/gen_antlr.sh b/gen_antlr.sh new file mode 100644 index 0000000..f743bac --- /dev/null +++ b/gen_antlr.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +root=$(dirname $0)/query +cd ${root} +antlr4 -visitor -Dlanguage=Python3 Query.g4 + diff --git a/guild_cog.py b/guild_cog.py new file mode 100644 index 0000000..c79be6e --- /dev/null +++ b/guild_cog.py @@ -0,0 +1,5 @@ +from discord.ext import commands + + +def setup(bot: commands.Bot): + pass diff --git a/launch.sh b/launch.sh new file mode 100755 index 0000000..d046481 --- /dev/null +++ b/launch.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +root=$(dirname $0) +cd ${root} +${root}/venv/bin/python ${root}/main.py "$@" &>$HOME/logs/discordbot + + + diff --git a/main.py b/main.py index 6385fa5..0b5ee9c 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ from functools import wraps from typing import List import discord.utils -from discord import Object +from discord import Object, Guild from discord.ext import commands import discord @@ -203,13 +203,18 @@ async def dump_roles(ctx: commands.Context, roles: List[discord.Role], quiet: bo @bot.command(pass_context=True, name="roles") @check_guild -async def roles_cmd(ctx: commands.Context, quiet: bool = False): - guild: discord.Guild = ctx.guild +async def roles_cmd(ctx: commands.Context, guild: Guild = None,quiet: bool = False): + if guild is None: + guild = ctx.guild roles: List[discord.Role] = guild.roles await dump_roles(ctx, roles, quiet) await ctx.react('✅') - +MODULES = [ + 'stream_cog' +] +for module in MODULES: + bot.load_extension(module) if __name__ == '__main__': with open('token.txt') as f: diff --git a/query/Query.g4 b/query/Query.g4 new file mode 100644 index 0000000..a13a80f --- /dev/null +++ b/query/Query.g4 @@ -0,0 +1,33 @@ + +grammar Query; + +prog: stat+ EOF? ; + +stat: expr # rawExpr + | ID '=' expr # assign + ; + +expr: expr '.' ID # access + | expr '(' (args=expr*) ')' # call + | expr op=('*'|'/') expr # MulDiv + | expr op=('+'|'-') expr # AddSub + | INT # int + | STRINGLITERAL # string + | ID # id + | '(' expr ')' # parens + | '{' (args=arguments)? prog '}' # func + ; + +arguments: ID (',' ID)* '->'; + +STRINGLITERAL : '"' ( StringEscapeSeq | ~( '\\' | '"' | '\r' | '\n' ) )* '"' ; +StringEscapeSeq : '\\' ( 't' | 'n' | 'r' | '"' | '\\' | '$' | ('0'..'9')) ; + +MUL : '*' ; // assigns token name to '*' used above in grammar +DIV : '/' ; +ADD : '+' ; +SUB : '-' ; +ID : [a-zA-Z]+ ; // match identifiers +INT : [0-9]+ ; // match integers +NEWLINE:'\r'? '\n' -> skip ; // return newlines to parser (is end-statement signal) +WS : [ \t]+ -> skip ; // toss out whitespace \ No newline at end of file diff --git a/query/__init__.py b/query/__init__.py new file mode 100644 index 0000000..1b51639 --- /dev/null +++ b/query/__init__.py @@ -0,0 +1 @@ +from .parse import parse diff --git a/query/parse.py b/query/parse.py new file mode 100644 index 0000000..b509044 --- /dev/null +++ b/query/parse.py @@ -0,0 +1,113 @@ +from typing import List + +from antlr4 import * + +from query.QueryLexer import QueryLexer +from query.QueryParser import QueryParser +from query.QueryVisitor import QueryVisitor + + +def make_function(outer_scope, argument_names: List[str], parser: QueryParser, prog: QueryParser.ProgContext): + def function(*args): + visitor = MyQueryVisitor(parser) + visitor.memory = outer_scope.copy() + for i in range(len(args)): + visitor.memory[argument_names[i]] = args[i] + return visitor.for_result(prog) + + return function + + +class Return(Exception): + def __init__(self, thing): + self.ret = thing + + +class MyQueryVisitor(QueryVisitor): + def __init__(self, parser: QueryParser): + self.memory = { + } + self.parser: QueryParser = parser + self.last_expr = None + + def for_result(self, ctx): + try: + self.visit(ctx) + except Return as ret: + return ret.ret + return self.last_expr + + def visitProg(self, ctx: QueryParser.ProgContext): + for stat in ctx.getChildren(lambda child: isinstance(child, QueryParser.StatContext)): + self.visit(stat) + + def visitFunc(self, ctx: QueryParser.FuncContext): + args = ctx.arguments().getText()[:-2].split(',') + return make_function(self.memory, args, self.parser, ctx.prog()) + + def visitAssign(self, ctx): + name = ctx.ID().getText() + value = self.visit(ctx.expr()) + self.memory[name] = value + return value + + def visitRawExpr(self, ctx: QueryParser.RawExprContext): + value = self.visit(ctx.expr()) + self.last_expr = value + return value + + def visitInt(self, ctx): + return int(ctx.INT().getText()) + + def visitId(self, ctx): + name = ctx.ID().getText() + if name in self.memory: + return self.memory[name] + return 0 + + def visitAccess(self, ctx: QueryParser.AccessContext): + thing = self.visit(ctx.expr()) + return getattr(thing, ctx.ID(), 0) + + def visitMulDiv(self, ctx): + left = (self.visit(ctx.expr(0))) + right = (self.visit(ctx.expr(1))) + if ctx.op.type == QueryParser.MUL: + return left * right + return left / right + + def visitString(self, ctx: QueryParser.StringContext): + return eval(ctx.getText()) + + def visitAddSub(self, ctx): + left = int(self.visit(ctx.expr(0))) + right = int(self.visit(ctx.expr(1))) + if ctx.op.type == QueryParser.ADD: + return left + right + return left - right + + def visitParens(self, ctx): + return self.visit(ctx.expr()) + + def visitCall(self, ctx: QueryParser.CallContext): + to_call = self.visit(ctx.expr(0)) + args = [self.visit(arg) for arg in ctx.getChildren(lambda x: isinstance(x, QueryParser.ExprContext))][1:] + return to_call(*args) + + def visit(self, tree): + return super(MyQueryVisitor, self).visit(tree) + + +def parse(text, **kwargs): + parser = QueryParser(CommonTokenStream(QueryLexer(InputStream(text)))) + tree = parser.prog() + visitor = MyQueryVisitor(parser) + for key, value in kwargs.items(): + visitor.memory[key] = value + return visitor.for_result(tree) + + +if __name__ == '__main__': + with open('debug.txt') as handle: + content = handle.read() + print(parse(content)) diff --git a/requirements.txt b/requirements.txt index db6f91f..950ae7f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ https://github.com/Rapptz/discord.py/archive/rewrite.zip +antlr4-python3-runtime diff --git a/stream_cog.py b/stream_cog.py new file mode 100644 index 0000000..20ae77c --- /dev/null +++ b/stream_cog.py @@ -0,0 +1,26 @@ +import discord +from discord.ext import commands +from discord.ext.commands import Context as CommandContext + +from query import parse + + +class StreamCog(object): + def __init__(self, bot): + self.bot: commands.Bot = bot + + @commands.command() + async def stream(self, ctx: CommandContext, *, to_eval): + val = parse(to_eval, + guild=ctx.guild, + channel=ctx.channel, + author=ctx.author, + bot=self.bot, + client=self.bot, + discord=discord, + ) + await ctx.send(repr(val)) + + +def setup(bot: commands.Bot): + bot.add_cog(StreamCog(bot)) -- cgit