Brainfuck Interpreter und Debugger

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Vom Thread http://www.python-forum.de/viewtopic.php?f=1&t=25360, hier nun im Showcase.

Code: Alles auswählen

#!/usr/bin/env python
from collections import defaultdict
import Tkinter as tkinter
import sys


class Brainfuck(object):
    
    def __init__(self, command="", text=""):
        self.command_index = 0
        self.command = command
        self.text = list(text)
        self.index = 0
        self.work = defaultdict(int)
        self.inter = {"," : self.get, "." : self.put, ">" : self.up,
                      "<" : self.down, "+" : self.add, "-" : self.sub,
                      "[" : self.right, "]" : self.left }
        self.opened_brackets = self._brackets()
        self.closed_brackets = self._brackets(True) 
    
    def _brackets(self, inverted=False):
        brackets = {}
        stack = []   
        for index, char in enumerate(self.command):
            if char == "[":
                stack.append(index)
            elif char == "]":
                if inverted:
                    brackets[index] = stack.pop()
                else:
                    brackets[stack.pop()] = index
        return brackets
        
    def run(self):
        while self.command_index < len(self.command):
            output = self.step()
            if output is not None:
                sys.stdout.write(output)

    def step(self):
        output = None
        if self.command_index < len(self.command):
            char = self.command[self.command_index]  
            if char in self.inter:
                output = self.inter[char]()
            self.command_index += 1
        return output

    def get(self):
        self.work[self.index] = ord(self.text.pop(0)) \
                                if len(self.text) > 0 else 0
        
    def put(self):
        return chr(self.work[self.index])

    def right(self):
        if self.work[self.index] == 0:
            self.command_index = self.opened_brackets[self.command_index]
            
    def left(self):
        if self.work[self.index] != 0:
            self.command_index = self.closed_brackets[self.command_index]

    def add(self):
        self.work[self.index] += 1

    def sub(self):
        self.work[self.index] -= 1
        
    def up(self):
        self.index += 1

    def down(self):
        self.index -= 1


class ListFrame(tkinter.Frame):

    def __init__(self, master, cnf=()):
        tkinter.Frame.__init__(self, master, cnf)
        pack_style = {"expand":True, "fill":"both", "padx":2, "pady":3}
        label = tkinter.Label(self, text="Array:", anchor="w")
        label.pack(fill="both", pady=2)
        
        vbar = tkinter.Scrollbar(self)
        vbar.pack(fill="y", side="right")
        
        self.listbox = tkinter.Listbox(self, yscrollcommand=vbar.set, width=10)
        self.listbox.pack(pack_style)
        
        vbar.config(command=self.listbox.yview)

    def clear(self):
        self.listbox.delete(0, "end")
        
    def append(self, value):
        self.listbox.insert("end", "{0:5}".format(value))
    
    def select(self, index):
        self.listbox.selection_clear(0, "end")
        self.listbox.selection_set(index)
        self.listbox.see(index)


class TaggedText(tkinter.Text):
    
    def __init__(self, master, cnf=()):
        tkinter.Text.__init__(self, master, cnf)
        self.current_index = 0
        self.selected = {"background":"#AA0000", "foreground":"#FFFFFF"}
        self.deselected = {"background":"#FFFFFF", "foreground":"#000000"}        
        
    def create_tags(self):
        width = self.cget("width")
        to_index = lambda num: "{0}.{1}".format(*divmod(num+width, width))
        for number in xrange(len(self.get(1.0, "end"))):
            self.tag_add(number, to_index(number), to_index(number+1))
        self.config(state="disable")
        self.select(0)
    
    def selection_clear(self):
        for number in xrange(len(self.get(1.0, "end"))):
            self.config(state="normal")
            self.tag_config(number, self.deselected)
            
    def select(self, index):
        self.tag_config(str(self.current_index), self.deselected)
        self.tag_config(str(index), self.selected)
        self.current_index = index


class BrainfuckDebugger(tkinter.Frame):

    def __init__(self, master, cnf=()):
        tkinter.Frame.__init__(self, master, cnf)
        self.command = None
        self.text = None
        self.output = None
        self.listframe = None
        self.brainfuck = None
        self.run_refresh = tkinter.IntVar(None, 1000)

    def update_list(self):        
        self.listframe.clear()
        index = 0
        for index in xrange(len(self.brainfuck.work)):
            self.listframe.append(self.brainfuck.work[index])
        if self.brainfuck.index > index:
            self.listframe.append(0)
        self.listframe.select(self.brainfuck.index)
        
    def start(self):
        self.command.create_tags()      
        self.text.create_tags()
        c, t = self.command.get(1.0, "end"), self.text.get(1.0, "end")
        self.brainfuck = Brainfuck(c, t)
    
    def stop(self):
        self.brainfuck = None
        self.command.selection_clear()
        self.text.selection_clear()
        self.listframe.clear()
        self.output.delete(1.0, "end")
        self
        
    def step(self):
        if self.brainfuck is None:
            return
        output =  self.brainfuck.step()
        if output is not None:
            self.output.insert("end", output)    
        self.command.select(self.brainfuck.command_index)
        self.text.select(len(self.text.get(1.0, "end")) - 
                         len(self.brainfuck.text))
        self.update_list()
        
    def run(self):
        if self.brainfuck is None:
            return
        self.step()
        self.after(self.run_refresh.get(), self.run)

    def create(self, command="", text=""):
        pack_style = {"expand":True, "fill":"both", "padx":2, "pady":3}
        inputframe = tkinter.Frame(self)
        
        label = tkinter.Label(inputframe, text="Brainfuck Command:", 
                              anchor="w")
        label.pack(pack_style)
        text_options = {"width":40, "height":7, "cursor":"xterm"}
        self.command = TaggedText(inputframe, text_options)
        self.command.insert("end", command)
        self.command.pack(pack_style)
        
        label = tkinter.Label(inputframe, text="Brainfuck Input:", anchor="w")
        label.pack(pack_style)
        self.text = TaggedText(inputframe, text_options)
        self.text.insert("end", text)
        self.text.pack(pack_style)
        
        label = tkinter.Label(inputframe, text="Brainfuck Output:", anchor="w")
        label.pack(pack_style)
        self.output = TaggedText(inputframe, text_options)
        self.output.pack(pack_style)

        start = tkinter.Button(inputframe, text="Start", command=self.start,
                               cursor="hand2")
        start.pack(pack_style, side="left")
        stop = tkinter.Button(inputframe, text="Stop", command=self.stop,
                              cursor="hand2")
        stop.pack(pack_style, side="left")
        step = tkinter.Button(inputframe, text="Step", command=self.step,
                              cursor="hand2")
        step.pack(pack_style, side="left")
        run = tkinter.Button(inputframe, text="Run", command=self.run,
                             cursor="hand2")
        run.pack(pack_style, side="left")
        options = {"label":"Speed: ", "from_":1, "to":3000, "resolution":1,
                   "orient":"horizontal", "variable":self.run_refresh}
        speed = tkinter.Scale(inputframe, options)
        speed.pack(pack_style)
        
        inputframe.pack(pack_style, side="left")
        self.listframe = ListFrame(self)
        self.listframe.pack(pack_style, side="left")


if __name__ == "__main__":
    brainfuck_command = ",.,[>+[>+>+<<-]>>[<<+>>-]<[<<+>>-]<<.,]"
    brainfuck_input = "Hello World"

    #Brainfuck(string, text).run()
    root = tkinter.Tk()
    root.title("Brainfuck Debugger")
    
    bd = BrainfuckDebugger(root)
    bd.create(brainfuck_command, brainfuck_input)
    bd.pack(expand=True, fill="both")
    
    root.mainloop()
Edit: Ein paar kleine Fehler behoben:
- die Liste scrollt jetzt selbstständig nach unten
- die Überprüfung der länge findet jetzt in "step" direkt statt und nicht in der GUI
- das Output Fenster und die Liste werden bei "stop" geleert
- die Laufgeschwindigkeit kann geändert werden
- das Aktualisieren der Liste in eine extra Methode gepackt.
Zuletzt geändert von Xynon1 am Sonntag 30. Januar 2011, 09:26, insgesamt 3-mal geändert.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

Hi,

schick, besonders der Debugger. Aber der Output: "Hfnos%]vzun" ist wohl nicht das, was du beabsichtigt hast, oder? Interpreter und Debugger scheinen tadellos zu funktionieren, irgendwo im Programm steckt noch der Wurm drin...
Hier ist mein Interpreter, wesentlich schlichter, ohne GUI und Debugger und als Funktion implementiert:
http://www.python-forum.de/pastebin.php?mode=view&s=140

Gruß
Norbert

Edit: ich habe eine alte Version mit Fehlern hochgeladen. Neue Fassung:
http://www.python-forum.de/pastebin.php?mode=view&s=141
Kann man Snippets mit Fehlern eigenlich wieder löschen?
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Doch genau das ist beabsichtigt, siehe hier: http://www.python-forum.de/viewtopic.php?f=11&t=25313
Ich finde deine Version, entschuldige bitte, auch nicht wesentlich schlichter, im gegenteil sie ist etwas undurschaubar. Bedenke, das man meine Klasse auch schnell so umbauen kann, zudem ich in der Dictionary nur sage was passieren soll, hast du für jedes einzelne ein "if" drin. Mit anderen Worten die Versionen nehmen sich beide nicht viel, mit dem Unterschied, das du an die einzelnen Schritte nicht von außen heran kommst. Wenn es dir um "schlicht" geht, dann sieh dir mal diese Implementierung, auf die mich sma hingewiesen hat, an: http://www.python-forum.de/viewtopic.ph ... 85#p102085
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

Hi,
Xynon1 hat geschrieben:Doch genau das ist beabsichtigt, siehe hier: http://www.python-forum.de/viewtopic.php?f=11&t=25313
Ach so, das hatte ich übersehen.
Xynon1 hat geschrieben:Ich finde deine Version, entschuldige bitte, auch nicht wesentlich schlichter, im gegenteil sie ist etwas undurschaubar. Bedenke, das man meine Klasse auch schnell so umbauen kann, zudem ich in der Dictionary nur sage was passieren soll, hast du für jedes einzelne ein "if" drin. Mit anderen Worten die Versionen nehmen sich beide nicht viel, mit dem Unterschied, das du an die einzelnen Schritte nicht von außen heran kommst. Wenn es dir um "schlicht" geht, dann sieh dir mal diese Implementierung, auf die mich sma hingewiesen hat, an: http://www.python-forum.de/viewtopic.ph ... 85#p102085
Puh, entschuldige bitte, wenn mein Beitrag als Kritik rüberkam, er war nicht so gemeint, noch nicht mal als Bewertung a la "schlicht ist besser als aufwendig". Es war einfach eine Feststellung, weil dein Programm mit GUI und Debugger einen ganz anderen Ansatz verfolgt als meines.
Kannst du präzisieren, was du an meinem Code undurchschaubar findest? Ich weiß, dass ich dazu neige, manches unnötig komplex zu formulieren und bin an Verbesserungsvorschlägen immer interessiert.
Was die Implementierung der Befehle anbelangt: sicher ist deine Methode flexibler, wenn es um Erweiterbarkeit geht, aber bei dem überschaubaren Befehlssatz von 8 Standardbefehlen hat meine Lösung mit den bedingten Verzweigungen sicher auch Ihre Berechtigung.

Gruß
Norbert
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

Einen ganz eigenen Reiz bekommt es, wenn man es darauf anlegt, das Ganze möglichst kurz zu halten: https://www.spoj.pl/problems/BRAINF_K/
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

@ntrunk
Deinen Ansatzt mit den Klammern, ist nicht besonders schön, da ist dir genau wie mir besser mit der Funktion von EyDu geholfen. Was ich undurchschaubar halte, nein das ist eigentlich das falsche Wort, was ich etwas unübersichtlich finde sind diese Funktionen in Funktionen. Sicher das mag hier weitestgehend korrekt sein, da die Funktionen so warscheinlich nirgendwo anders genutzt werden können, aber wäre es der Übersichtlichkeit, nicht zuträglicher die Funktionen in den Modulbereich zu setzen? Dann würde man auch die while-Schleife sofort erkennen, was IMHO sehr wichtig ist, denn wenn ich eine Funktion aufrufe irgendwo die eine längere while-Schleife enthält, möchte ich das sehen und nicht erst eine Seite nach unten Scrollen und suchen ob da ein while drin ist. Das geht halt bei dir ein wenig unter. Ansonsten sieht es doch ganz in Ordnung aus. Im übrigen finde ich deinen Ansatzt nun nicht sonderlich verschieden von meinem, wenn du aus dem "command"-ifs eine Dictionary machst welche auf Funktionen enthält, würde dein Programm fast genauso ausehen.

@numerix & ntrunk
Das die Werte im "Array" auf 256 beschränkt sind wusste ich gar nicht(macht aber Sinn), muss ich noch hinzufügen.

Zudem fehlen mir noch Kommentare, bisher werden ja Zeichen nur ignoriert, aber wenn in einem Kommentar +-<>[],. vorkommen sollte, ist das nicht besonders gut und ich muss noch negative Indexe verhindern.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
ntrunk
User
Beiträge: 83
Registriert: Sonntag 7. September 2008, 23:09
Wohnort: Buchen (Odenwald)

@numerix:
Verstehe ich das richtig, du hast einen Interpreter mit einem Codeumfang von nur 218 Bytes geschrieben? Das ist beeindruckend! Ist es zu viel verlangt, wenn ich dich um einen Tipp bitte, welchen Ansatz du verfolgt hast?

@Xynon1:
Klammerimplementierung:
Ich verstehe nicht, was an meinem Ansatz unschön sein soll, kannst du das näher erklären?
Dein Code liest alle Klammerzuordnungen beim Programmstart in ein dict ein und hat dadurch die Möglichkeit, beim Abarbeiten des Codes über den Index als Key die passende öffnende oder schliessende Klammer zu finden. Okay, das ist eine praktikable und elegante Lösung.
Mein Code merkt sich beim Auftreten einer öffnenden Klammer die Position auf einem Stapel und kann beim Auftreten einer schliessenden Klammer an die Position der letzten öffnenden zurückspringen. Eine Suche nach einer passenden Klammer wird nur dann gemacht, wenn sie nötig ist, nämlich wenn die Schleife übersprungen werden soll. Das halte ich für ebenso praktikabel und elegant. :wink:

lokale Funktionen:
Da bin ich völlig anderer Meinung, m.E. erhöhen lokale Funktionen eher die Übersichtlichkeit des Codes. Wenn ich einen Programmablauf verstehen will, dann lenken mich diese Funktionen nicht vom Fluss des Hauptprogramms ab, weil sie ein Implementierungsdetail der jeweiligen Funktion sind. Wenn ich diese Funktionen jetzt auf Modulebene stelle, ist das nicht mehr offensichtlich. Beim Lesen des Programmcodes muss ich diese Einstufung als Implementierungsdetail der übergeordneten Funktion erst erkennen, sie lenken mich also zunächst erst mal eine gewisse Zeit lang vom Verstehen des Hauptprogramms ab.

Gruß
Norbert
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

ntrunk hat geschrieben:Verstehe ich das richtig, du hast einen Interpreter mit einem Codeumfang von nur 218 Bytes geschrieben?
Das hast du im Prinzip richtig verstanden.
ntrunk hat geschrieben:Ist es zu viel verlangt, wenn ich dich um einen Tipp bitte, welchen Ansatz du verfolgt hast?
SPOJ funktioniert so, dass die hochgeladenen Programme unmittelbar nach dem Hochladen ausgeführt und mit einem (in der Regel unbekannten) Input gefüttert werden. Im konkreten Fall ist das offenbar recht viel Input, so dass man es mit einem echten BF-Interpreter nicht im timelimit schafft (nur mit psyco). Daher habe ich die Interpreter-Idee nicht lange weiterverfolgt und es als Compiler-Variante umgesetzt. Der Weg zu den 218 Bytes war allerdings ein langer ...
Antworten