Skip to content

Instantly share code, notes, and snippets.

@moriyoshi
Last active December 17, 2015 19:38
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save moriyoshi/5661397 to your computer and use it in GitHub Desktop.
Save moriyoshi/5661397 to your computer and use it in GitHub Desktop.
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'))
# 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