Supybot-plugins/Brainfuck/plugin.py

213 lines
7.8 KiB
Python

###
# Copyright (c) 2011, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import time
import supybot.utils as utils
from supybot.commands import *
import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
from supybot.i18n import PluginInternationalization, internationalizeDocstring
_ = PluginInternationalization('Brainfuck')
class BrainfuckException(Exception):
pass
class BrainfuckSyntaxError(BrainfuckException):
pass
class BrainfuckTimeout(BrainfuckException):
pass
class NotEnoughInput(BrainfuckException):
pass
class SegmentationFault(BrainfuckException):
pass
class InvalidCharacter(BrainfuckException):
pass
class BrainfuckProcessor:
def __init__(self, dummy=False):
self._dummy = dummy
if not dummy:
self.memory = [0]
self.memoryPointer = 0
def checkSyntax(self, program):
nesting = 0
index = 0
for char in program:
index += 1
if char == '[':
nesting += 1
elif char == ']':
nesting -= 1
if nesting < 0:
return _('Got `]` (at index %i), expected whatever you '
'want but not that.') % index
if nesting != 0:
return _('Got end of string, expected `]`.')
return
def execute(self, program, input_='', timeLimit=5, checkSyntax=True):
if checkSyntax:
syntaxError = self.checkSyntax(program)
if syntaxError:
raise BrainfuckSyntaxError(syntaxError)
programPointer = 0
output = ''
programLength = len(program)
input_ = [ord(x) for x in input_]
loopStack = []
timeout = time.time() + timeLimit
while programPointer < programLength:
char = program[programPointer]
if char == '>': # Increment pointer
self.memoryPointer += 1
if len(self.memory) <= self.memoryPointer:
self.memory.append(0)
elif char == '<': # Decrement pointer
self.memoryPointer -= 1
if self.memoryPointer < 0:
raise SegmentationFault(_('Negative memory pointer.'))
elif char == '+': # Increment data
self.memory[self.memoryPointer] += 1
elif char == '-': # Decrement data
self.memory[self.memoryPointer] -= 1
elif char == '.': # Output data
try:
output += chr(self.memory[self.memoryPointer])
except ValueError:
raise InvalidCharacter(str(self.memory[self.memoryPointer]))
elif char == ',': # Input data
try:
self.memory[self.memoryPointer] = input_.pop(0)
except IndexError:
raise NotEnoughInput()
elif char == '[': # Loop start
if not self.memory[self.memoryPointer]:
nesting = 0
while programPointer < programLength:
if program[programPointer] == '[':
nesting += 1
elif program[programPointer] == ']':
nesting -= 1
if nesting == 0:
break
programPointer += 1
else:
loopStack.append(programPointer)
elif char == ']': # Loop end
programPointer = loopStack.pop() - 1
programPointer += 1
if timeout < time.time():
raise BrainfuckTimeout(output)
return output
@internationalizeDocstring
class Brainfuck(callbacks.Plugin):
"""Add the help for "@plugin help Brainfuck" here
This should describe *how* to use this plugin."""
threaded = True
latestProcessor = None
@internationalizeDocstring
def checksyntax(self, irc, msg, args, code):
"""<command>
Tests the Brainfuck syntax without running it. You should quote the
code if you use brackets, because Supybot would interpret it as nested
commands."""
syntaxError = BrainfuckProcessor(dummy=True).checkSyntax(code)
if syntaxError:
irc.reply(syntaxError)
else:
irc.reply(_('Your code looks ok.'))
checksyntax = wrap(checksyntax, ['text'])
@internationalizeDocstring
def brainfuck(self, irc, msg, args, opts, code):
"""[--recover] [--input <characters>] <command>
Interprets the given Brainfuck code. You should quote the code if you
use brackets, because Supybot would interpret it as nested commands.
If --recover is given, the bot will recover the previous processor
memory and memory pointer.
The code will be fed the <characters> when it asks for input."""
opts = dict(opts)
if 'input' not in opts:
opts['input'] = ''
if 'recover' in opts:
if self.latestProcessor is None:
irc.error(_('No processor has been run for the moment.'))
return
else:
processor = self.latestProcessor
else:
processor = BrainfuckProcessor()
self.latestProcessor = processor
try:
output = processor.execute(code, input_=opts['input'])
except BrainfuckSyntaxError as e:
irc.error(_('Brainfuck syntax error: %s') % e.args[0])
return
except BrainfuckTimeout as e:
if e.args[0] != '':
irc.reply(e.args[0])
irc.error(_('Brainfuck processor timed out.'))
return
except NotEnoughInput:
irc.error(_('Input too short.'))
return
except SegmentationFault as e:
irc.error(_('Segmentation fault: %s') % e.args[0])
return
except InvalidCharacter as e:
irc.error(_('Tried to output invalid character: %s') % e.args[0])
return
irc.reply(output)
brainfuck = wrap(brainfuck, [getopts({'recover': '',
'input': 'something'}),
'text'])
Class = Brainfuck
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: