Last active
December 17, 2015 19:38
-
-
Save moriyoshi/5661397 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
from saying.exceptions import ApplicationException | |
from itertools import chain | |
class DSLSyntaxError(ApplicationException): | |
pass | |
def format(handler, text, message): | |
var = { | |
u'sender': lambda: resolve_handle(message.Sender.Handle), | |
u'random': lambda: resolve_handle(random.choice(filter(lambda m: m != handler.skype.User(), message.Chat.Members)).Handle), | |
} | |
def repl(g): | |
placeholder = g.group(1) | |
return var.get(placeholder, lambda:u'')() | |
return re.sub(ur'{([^}]*)}', repl, text) | |
def unescape(text): | |
return text.replace('\\', '') | |
def tokenize(text): | |
for token in re.finditer(ur"""(\s+)|([^\s,;"'(){}0-9][^\s,;"'(){}]*)|("(?:[^"]|\\.)+")|('(?:[^']|\\.)+')|((?:[0-9]+(?:\.[0-9]+)?|\.[0-9]+)(?:[eE][+-]?[0-9]*)?)|([,();{}])""", text): | |
ws = token.group(1) | |
identifier = token.group(2) | |
string_literal = token.group(3) or token.group(4) | |
numerics = token.group(5) | |
others = token.group(6) | |
if ws: | |
yield 'SPACE', ws, token.start(0), ws | |
elif identifier: | |
yield 'IDENTIFIER', identifier, token.start(0), identifier | |
elif string_literal: | |
yield 'STRING', unescape(string_literal[1:-1]), token.start(0), string_literal | |
elif numerics: | |
yield 'NUMBER', numerics, token.start(0), numerics | |
elif others in (',', '(', ')', '{', '}', ';'): | |
yield others, others, token.start(0), others | |
yield 'EOF', None, len(text), u'' | |
def parse(text): | |
ctx = {} | |
tokens = tokenize(text) | |
_, statements = parse_statements(ctx, tokens) | |
return statements | |
def parse_statements(ctx, tokens): | |
statements = [] | |
while True: | |
token, statement = parse_statement(ctx, tokens) | |
statements.append(statement) | |
if token[0] == 'EOF': | |
break | |
return token, statements | |
def parse_statement(ctx, tokens): | |
return parse_expr(ctx, tokens) | |
def parse_identifier_or_combo_expr(ctx, tokens, simplify): | |
items = [] | |
image = [] | |
for token in tokens: | |
if token[0] in ('IDENTIFIER', 'STRING', 'NUMBER'): | |
items.append(token) | |
image.append(token[3]) | |
elif token[0] == '{': | |
image.append(token[3]) | |
token, combo = parse_expr(ctx, tokens, ['}'], False) | |
items.append(combo) | |
image.append(combo[3]) | |
image.append(token[3]) | |
else: | |
break | |
if simplify and len(items) == 1 and items[0][0] in ('IDENTIFIER', 'STRING', 'NUMBER'): | |
return token, items[0] | |
else: | |
return token, ('COMBO', items, None, u''.join(image)) | |
def parse_expr(ctx, tokens, terminated_by=[';', 'EOF'], simplify=True): | |
token = tokens.next() | |
if token[0] in ('IDENTIFIER', 'STRING', 'NUMBER', '{'): | |
token, name = parse_identifier_or_combo_expr(ctx, chain([token], tokens), simplify) | |
return parse_function(ctx, chain([token], tokens), name, terminated_by) | |
else: | |
raise DSLSyntaxError("IDENTIFIER, STRING, NUMBER, '{' expected, got %s at column %d" % (token[0], token[2])) | |
def parse_function(ctx, tokens, name, terminated_by): | |
token = tokens.next() | |
image = [name[3]] | |
if token[0] == 'SPACE': | |
image.append(token[3]) | |
token = tokens.next() | |
if token[0] == '(': | |
image.append(token[3]) | |
token, arguments = parse_function_arguments(ctx, tokens, [')']) | |
image.append(arguments[3]) | |
image.append(token[3]) | |
token = tokens.next() | |
elif token[0] == ',' or token[0] in terminated_by: | |
return token, name | |
else: | |
token, arguments = parse_function_arguments(ctx, chain([token], tokens), terminated_by) | |
image.append(arguments[3]) | |
image.append(token[3]) | |
return token, ('FUNCTION', (name, arguments), None, u''.join(image)) | |
def parse_function_arguments(ctx, tokens, terminated_by): | |
arguments = [] | |
image = [] | |
while True: | |
token = tokens.next() | |
if token[0] in terminated_by: | |
break | |
elif token[0] == ',': | |
arguments.append(None) | |
image.append(token[3]) | |
elif token[0] in ('IDENTIFIER', 'STRING', 'NUMBER', '{'): | |
token, expr = parse_expr(ctx, chain([token], tokens), terminated_by) | |
image.append(expr[3]) | |
if token[0] == 'SPACE': | |
token = tokens.next() | |
image.append(token[3]) | |
if token[0] != ',' and token[0] not in terminated_by: | |
raise DSLSyntaxError("',' expected, got %s at column %d" % (token[0], token[2])) | |
arguments.append(expr) | |
if token[0] in terminated_by: | |
break | |
image.append(token[3]) | |
elif token[0] == 'SPACE': | |
image.append(token[3]) | |
else: | |
raise DSLSyntaxError("IDENTIFIER, STRING, NUMBER, '{', SPACE or ',' expected, got %s at column %d" % (token[0], token[2])) | |
return token, ('ARGUMENTS', arguments, None, u''.join(image)) | |
if __name__ == '__main__': | |
from pprint import pprint | |
pprint(parse('+ 1, 2')) | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# encoding: utf-8 | |
""" | |
#saying | |
""" | |
import re | |
import sys | |
from models import SayingUser, Saying | |
from skypehub.handlers import on_message | |
from botutils import resolve_handle, silent_if_other_bot_exists | |
import random | |
import operator | |
from saying.exceptions import ApplicationException | |
from saying.parser import parse | |
from suddendeath import suddendeathmessage | |
def fn_sender(handler, message, args): | |
return resolve_handle(message.Sender.Handle) | |
def fn_random(handler, message, args): | |
return resolve_handle(random.choice(filter(lambda m: m != handler.skype.User(), message.Chat.Members)).Handle) | |
def fn_saying(handler, message, args): | |
if len(args) != 1: | |
return u'saying' | |
if args[0][0] != 'FUNCTION' or args[0][1][0][0] != 'IDENTIFIER': | |
return u'saying' | |
subcommand = args[0][1][0][1] | |
if subcommand not in (u'create', u'delete', u'list'): | |
raise ApplicationException(u"#saying create|delete handle") | |
if subcommand == u'create': | |
handle = args[0][1][1][1][0][1] | |
try: | |
SayingUser(handle=handle).save() | |
message.Chat.SendMessage(u"#%sを登録しました" % handle) | |
except: | |
raise ApplicationException(u"%s語録はすでに登録されています" % handle) | |
elif subcommand == u'delete': | |
handle = args[0][1][1][1][0][1] | |
user = SayingUser.objects.get(handle=handle) | |
if user is None: | |
raise ApplicationException(u"%s語録は登録されていません" % handle) | |
Saying.objects.filter(handle=handle).delete() | |
user.delete() | |
message.Chat.SendMessage(u"#%sを削除しました" % handle) | |
elif subcommand == u'list': | |
handle = args[0][1][1][1][0][1] | |
msg = u'' | |
for saying in Saying.objects.filter(handle=handle): | |
msg += u'%s: %s\n' % (resolve_handle(saying.who), saying.text) | |
message.Chat.SendMessage(msg) | |
return None | |
def fn_suddendeath(handler, message, args): | |
return suddendeathmessage(evaluate(handler, message, args[0])) | |
def gen_binary_fn(operator): | |
def fn(handler, message, args): | |
if len(args) != 2: | |
raise ApplicationException('Wrong number of arguments') | |
return operator(evaluate(handler, message, args[0]), evaluate(handler, message, args[1])) | |
return fn | |
class NotEvaluated(Exception): | |
pass | |
def fn__default(handler, message, handle, args): | |
if handle.endswith('_delete'): | |
handle_ = handle[:-7] | |
try: | |
SayingUser.objects.get(handle=handle_) | |
if len(args) == 0: | |
return handle | |
text = u';'.join(arg[3] for arg in args) | |
Saying.objects.filter(handle=handle_, text=text).delete() | |
message.Chat.SendMessage(u"%s語録から「%s」を削除しました" % (handle_, text)) | |
except SayingUser.DoesNotExist: | |
message.Chat.SendMessage(u"%s語録はありません" % (handle_,)) | |
return None | |
else: | |
try: | |
SayingUser.objects.get(handle=handle) | |
if len(args) == 0: | |
sayings = Saying.objects.filter(handle=handle).all() | |
retval = [] | |
script = random.choice(list(sayings)).text | |
statements = parse(script) | |
if len(statements) == 1 and statements[0][0] == 'IDENTIFIER' and statements[0][1] == script: | |
retval.append(script) | |
else: | |
for statement in statements: | |
retval.append(evaluate(handler, message, statement)) | |
return u';'.join(retval) | |
else: | |
try: | |
text = u';'.join(arg[3] for arg in args) | |
Saying(handle=handle, text=text, who=message.Sender.Handle).save() | |
message.Chat.SendMessage(u"%s語録に「%s」を登録しました" % (handle, text)) | |
return None | |
except: | |
raise ApplicationException(u'%sはすでに登録されています' % text) | |
except SayingUser.DoesNotExist: | |
return handle | |
raise NotEvaluated | |
namespace = { | |
u'sender': fn_sender, | |
u'random': fn_random, | |
u'saying': fn_saying, | |
u'suddendeath': fn_suddendeath, | |
u'+': gen_binary_fn(operator.add), | |
u'-': gen_binary_fn(operator.sub), | |
u'*': gen_binary_fn(operator.mul), | |
u'/': gen_binary_fn(operator.div), | |
} | |
def evaluate(handler, message, node): | |
if node[0] == 'FUNCTION': | |
if node[1][0][0] == 'IDENTIFIER': | |
name = node[1][0][1] | |
else: | |
name = evaluate(handler, message, node[1][0]) | |
fn = namespace.get(name) | |
if fn is not None: | |
value = fn(handler, message, node[1][1][1]) | |
else: | |
try: | |
value = fn__default(handler, message, name, node[1][1][1]) | |
except NotEvaluated: | |
value = name | |
elif node[0] == 'COMBO': | |
value = u''.join(unicode(evaluate(handler, message, item)) for item in node[1]) | |
elif node[0] == 'NUMBER': | |
value = float(node[1]) | |
elif node[0] == 'STRING': | |
value = node[1] | |
elif node[0] == 'IDENTIFIER': | |
fn = namespace.get(node[1]) | |
if fn is not None: | |
value = fn(handler, message, []) | |
else: | |
try: | |
value = fn__default(handler, message, node[1], []) | |
except NotEvaluated: | |
value = node[1] | |
else: | |
value = node[0] | |
return value | |
def receiver(handler, message, status): | |
if status != 'RECEIVED': | |
return | |
if message.Body[0] != u'#': | |
return | |
try: | |
statements = parse(message.Body[1:]) | |
messages = [] | |
for statement in statements: | |
m = evaluate(handler, message, statement) | |
if m is not None: | |
messages.append(unicode(m)) | |
if messages: | |
message.Chat.SendMessage(u' '.join(messages)) | |
except ApplicationException as e: | |
message.Chat.SendMessage(e.args[0]) | |
on_message.connect(receiver) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment