Scheme: Add support for lambda functions.

master
Valentin Lorentz 2013-02-18 21:31:44 +01:00
parent 33a9887ef5
commit 13698d331a
2 changed files with 138 additions and 43 deletions

View File

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

View File

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