Scheme: Add support for lambda functions.
parent
33a9887ef5
commit
13698d331a
167
Scheme/plugin.py
167
Scheme/plugin.py
|
@ -29,7 +29,9 @@
|
|||
###
|
||||
|
||||
import ast
|
||||
import copy
|
||||
import operator
|
||||
import fractions
|
||||
import functools
|
||||
import collections
|
||||
|
||||
|
@ -42,6 +44,14 @@ from supybot.i18n import PluginInternationalization, internationalizeDocstring
|
|||
|
||||
_ = PluginInternationalization('Scheme')
|
||||
|
||||
NUMBER_TYPES = (
|
||||
('integer', int),
|
||||
('rational', fractions.Fraction),
|
||||
('real', float),
|
||||
('complex', complex),
|
||||
('number', str),
|
||||
)
|
||||
|
||||
class SchemeException(Exception):
|
||||
pass
|
||||
|
||||
|
@ -49,7 +59,22 @@ def eval_argument(arg, env):
|
|||
if isinstance(arg, list):
|
||||
return eval_scheme(arg, env)
|
||||
else:
|
||||
return env[arg] if arg in env else ast.literal_eval(arg)
|
||||
if arg in env:
|
||||
return eval_argument(env[arg], {})
|
||||
else:
|
||||
for name, parser in NUMBER_TYPES:
|
||||
try:
|
||||
return parser(arg)
|
||||
except ValueError:
|
||||
pass
|
||||
# You shall not pass
|
||||
raise SchemeException(_('Unbound variable: %s') % arg)
|
||||
|
||||
def py2scheme(tree):
|
||||
if isinstance(tree, list):
|
||||
return '(%s)' % (' '.join(map(py2scheme, tree)))
|
||||
else:
|
||||
return str(tree)
|
||||
|
||||
def schemify_math(f):
|
||||
# Makes a two-arguments function an *args function, with correct
|
||||
|
@ -61,59 +86,121 @@ def schemify_math(f):
|
|||
return f(args[0], args[1])
|
||||
def newf(tree, env):
|
||||
return rec(map(functools.partial(eval_argument, env=env), tree[1:]))
|
||||
newf.__name__ = 'schemified_%s' % f.__name__
|
||||
return newf
|
||||
|
||||
ARGUMENTS_ERROR = _('%s takes %s %i arguments not %i (in (%s))')
|
||||
def scm_lambda(tree, env):
|
||||
try:
|
||||
self, args, expr = tree
|
||||
except ValueError:
|
||||
raise SchemeException(ARGUMENTS_ERROR %
|
||||
('lambda', _('exactly'), 2, len(tree)-1, ' '.join(tree)))
|
||||
if not isinstance(args, list):
|
||||
args = ['.', args]
|
||||
try:
|
||||
if args.index('.') != len(args)-2:
|
||||
raise SchemeException(_('Invalid arguments list: (%s)') %
|
||||
' '.join(args))
|
||||
rest = args[-1]
|
||||
args = args[0:-2]
|
||||
except ValueError: # No rest
|
||||
rest = None
|
||||
def f(tree2, env2):
|
||||
self2, args2 = tree2[0], tree2[1:]
|
||||
arguments_error = ARGUMENTS_ERROR % \
|
||||
(self2, '%s', len(args), len(args2), tree2)
|
||||
env3 = env2.copy()
|
||||
if len(args2) < len(args):
|
||||
raise SchemeException(arguments_error %
|
||||
_('at least') if rest else _('exactly'))
|
||||
elif not rest and len(args2) > len(args):
|
||||
raise SchemeException(arguments_error % _('exactly'))
|
||||
else:
|
||||
env3.update(dict(zip(args, args2)))
|
||||
if rest:
|
||||
env3.update({rest: args2[len(args):]})
|
||||
return eval_scheme(expr, env3)
|
||||
f.__name__ = 'scheme_%s' % py2scheme(tree)
|
||||
return f
|
||||
|
||||
DEFAULT_ENV = [
|
||||
('lambda', scm_lambda),
|
||||
]
|
||||
# Add some math operators
|
||||
DEFAULT_ENV = map(lambda (x,y):(x, schemify_math(y)), (
|
||||
DEFAULT_ENV += map(lambda (x,y):(x, schemify_math(y)), (
|
||||
('+', operator.add),
|
||||
('-', operator.sub),
|
||||
('*', operator.mul),
|
||||
('/', operator.div),
|
||||
))
|
||||
|
||||
DEFAULT_ENV = dict(DEFAULT_ENV)
|
||||
|
||||
|
||||
def parse_scheme(code, start=0, end=None, tree=[], wrap=False):
|
||||
if wrap:
|
||||
wrapper = lambda x:[x]
|
||||
else:
|
||||
wrapper = lambda x:x
|
||||
def parse_scheme(code, start=0, end=None, unpack=False):
|
||||
if end is None:
|
||||
end = len(code)-1
|
||||
while code[start] == ' ':
|
||||
start += 1
|
||||
while code[end] == ' ':
|
||||
end -= 1
|
||||
if code[start] == '(' and code[end] == ')':
|
||||
return parse_scheme(code, start+1, end-1, tree, True)
|
||||
else:
|
||||
level = 0
|
||||
in_string = False
|
||||
escaped = False
|
||||
for i in xrange(start, end):
|
||||
if code[i] == '"' and not escaped:
|
||||
in_string = not in_string
|
||||
elif in_string:
|
||||
pass
|
||||
elif code[i] == '\'':
|
||||
escaped = not escaped
|
||||
elif code[i] == '(':
|
||||
level += 1
|
||||
elif code[i] == ')':
|
||||
if level == 0:
|
||||
raise SchemeException(_('At index %i, unexcepted `)\'.')
|
||||
% end)
|
||||
level -=1
|
||||
elif level == 0 and code[i] == ' ' and start != i:
|
||||
return wrapper(parse_scheme(code, start, i,
|
||||
parse_scheme(code, i+1, end)))
|
||||
else:
|
||||
continue # Nothing to do
|
||||
if level != 0:
|
||||
raise SchemeException(_('Unclosed parenthesis.'))
|
||||
c = code[end]
|
||||
return [code[start:end] + (c if c!=' ' else '')] + tree
|
||||
return parse_scheme(code, start+1, end-1, unpack=False)
|
||||
level = 0
|
||||
in_string = False
|
||||
escaped = False
|
||||
tokens = []
|
||||
token_start = start
|
||||
for i in xrange(start, end+1):
|
||||
if code[i] == '"' and not escaped:
|
||||
in_string = not in_string
|
||||
elif in_string:
|
||||
pass
|
||||
elif code[i] == '\'':
|
||||
escaped = not escaped
|
||||
elif code[i] == '(':
|
||||
level += 1
|
||||
elif code[i] == ')':
|
||||
level -=1
|
||||
if level == -1:
|
||||
raise SchemeException(_('At index %i, unexpected `)\' near %s')
|
||||
% (end, code[max(0, end-10):end+10]))
|
||||
elif level == 0:
|
||||
tokens.append(parse_scheme(code, token_start, i))
|
||||
token_start = i+1
|
||||
elif level == 0 and code[i] == ' ' and token_start != i:
|
||||
tokens.append(parse_scheme(code, token_start, i))
|
||||
token_start = i+1
|
||||
else:
|
||||
continue # Nothing to do
|
||||
if level != 0:
|
||||
raise SchemeException(_('Unclosed parenthesis in: %s') %
|
||||
code[start:end+1])
|
||||
if start == token_start:
|
||||
return code[start:end+1]
|
||||
elif start < end:
|
||||
tokens.append(parse_scheme(code, token_start, end))
|
||||
tokens = filter(bool, tokens)
|
||||
if unpack:
|
||||
assert len(tokens) == 1, tokens
|
||||
tokens = tokens[0]
|
||||
return tokens
|
||||
|
||||
def eval_scheme(tree, env=DEFAULT_ENV):
|
||||
return env[tree[0]](tree, env.copy())
|
||||
if isinstance(tree, str) and tree in env:
|
||||
return env[tree]
|
||||
first = eval_scheme(tree[0])
|
||||
if callable(first):
|
||||
return first(tree, env.copy())
|
||||
else:
|
||||
return tree
|
||||
|
||||
def eval_scheme_result(tree):
|
||||
if isinstance(tree, list):
|
||||
return '(%s)' % ' '.join(map(eval_scheme_result, tree))
|
||||
else:
|
||||
return str(eval_argument(tree, []))
|
||||
|
||||
@internationalizeDocstring
|
||||
class Scheme(callbacks.Plugin):
|
||||
|
@ -127,14 +214,14 @@ class Scheme(callbacks.Plugin):
|
|||
|
||||
Evaluates Scheme."""
|
||||
try:
|
||||
tree = parse_scheme(code)[0]
|
||||
tree = parse_scheme(code)
|
||||
except SchemeException as e:
|
||||
irc.error('Syntax error: ' + e.args[0])
|
||||
irc.error('Syntax error: ' + e.args[0], Raise=True)
|
||||
try:
|
||||
result = eval_scheme(tree)
|
||||
except SchemeException as e:
|
||||
irc.error('Runtime error: ' + e.args[0])
|
||||
irc.reply(result)
|
||||
irc.error('Runtime error: ' + e.args[0], Raise=True)
|
||||
irc.reply(eval_scheme_result(result))
|
||||
scheme = wrap(scheme, ['text'])
|
||||
|
||||
Class = Scheme
|
||||
|
|
|
@ -36,15 +36,23 @@ class SchemeTestCase(PluginTestCase):
|
|||
plugins = ('Scheme',)
|
||||
|
||||
def testParse(self):
|
||||
self.assertEqual(plugin.parse_scheme('(+ 11 12)')[0],
|
||||
self.assertEqual(plugin.parse_scheme('(+ 11 12)'),
|
||||
['+', '11', '12'])
|
||||
self.assertEqual(plugin.parse_scheme('(+ 5 4)')[0],
|
||||
self.assertEqual(plugin.parse_scheme('(+ 5 4)'),
|
||||
['+', '5', '4'])
|
||||
self.assertEqual(plugin.parse_scheme('(+ 5 (* 4 6))')[0],
|
||||
self.assertEqual(plugin.parse_scheme('(+ 5 (* 4 6))'),
|
||||
['+', '5', ['*', '4', '6']])
|
||||
self.assertEqual(plugin.parse_scheme('((lambda x x) 1 2 3)')[1:],
|
||||
['1', '2', '3'])
|
||||
self.assertEqual(plugin.parse_scheme('((lambda (x y) (+ x y)) 11 12)'),
|
||||
[['lambda', ['x', 'y'], ['+', 'x', 'y']], '11', '12'])
|
||||
def testEval(self):
|
||||
self.assertResponse('scheme (+ 11 12)', '23')
|
||||
self.assertResponse('scheme (+ 5 4 2)', '11')
|
||||
self.assertResponse('scheme (+ 5 (* 5 2))', '15')
|
||||
|
||||
def testLambda(self):
|
||||
self.assertResponse('scheme ((lambda x x) 1 2 3)', '(1 2 3)')
|
||||
self.assertResponse('scheme ((lambda (x y) (+ x y)) 11 12)', '23')
|
||||
|
||||
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
|
||||
|
|
Loading…
Reference in New Issue