Brainfuck Interpreter - problem z zagnieżdżonymi pętlami

Brainfuck Interpreter - problem z zagnieżdżonymi pętlami
Shizzer
  • Rejestracja:prawie 8 lat
  • Ostatnio:5 miesięcy
  • Postów:231
0

Witam

Od paru dni męczę się z implementacją algorytmu dotyczącego zagnieżdżonych pętli w Brainfucku. Napisałem program, który "interpretuje" kod Brainfucka, ale bez uwzględniania zagnieżdżonych pętli. Chciałbym żeby interpreter był jak najlepiej dopracowany dlatego proszę Was o pomoc. Problem przedstawię dość szczegółowo, aby nic nie zabrakło. To jest cały kod programu:

Kopiuj
import sys


def execute(filename):
    f = open(filename, 'r')
    translate(f.read())
    f.close()


def translate(commands):

    bf_array, cell_pointer, bf_code_pointer = [0], 0, 0,
    bf_code = list(commands)
    tmp_bfstack = []        # pozycje nawiasow

    while bf_code_pointer < len(bf_code):
        if bf_code[bf_code_pointer] == '>':
            bf_array.append(0)
            cell_pointer += 1
        if bf_code[bf_code_pointer] == '<':
            cell_pointer -= 1
        if bf_code[bf_code_pointer] == '+':
            bf_array[cell_pointer] += 1
        if bf_code[bf_code_pointer] == '-':
            bf_array[cell_pointer] -= 1
        if bf_code[bf_code_pointer] == '.': print(chr(bf_array[cell_pointer]))
        if bf_code[bf_code_pointer] == ',': bf_array[cell_pointer] = ord(input())
        if bf_code[bf_code_pointer] == '[':
            tmp_bfstack.append(bf_code_pointer)
            print(tmp_bfstack)                # TEST
        if bf_code[bf_code_pointer] == ']' and bf_array[cell_pointer] != 0:
            bf_code_pointer = tmp_bfstack.pop()

        bf_code_pointer += 1
        print(bf_code_pointer)               # TEST


def main():
    if len(sys.argv) == 2: execute(sys.argv[1])
    else: print('Try:', sys.argv[0], 'filename.')


main()


W tym fragmencie jest problem:

Kopiuj
 if bf_code[bf_code_pointer] == '[':
            tmp_bfstack.append(bf_code_pointer)
            print(tmp_bfstack)                # TEST
 if bf_code[bf_code_pointer] == ']' and bf_array[cell_pointer] != 0:
            bf_code_pointer = tmp_bfstack.pop()

 bf_code_pointer += 1

Mianowicie pomyślałem, że program ma działać w następujący sposób:
Do listy dodaje pozycję '[' za każdym razem gdy go znajdzie. Jak wiadomo '[' jest punktem inicjalizującym pętlę, a ']' jest punktem kończącym jej działanie. Toteż logika ma być przykładowo taka: >>+[-->><++] '[' jest na pozycji 3, więc 3 zostaje zapisana do listy tmp_bfstack. Gdy while dotrze do ']' wówczas ustawiamy wskaźnik na pozycję 3 i rozpoczynamy wykonywanie instrukcji między nawiasami do momentu aż wartość bf_array[cell_pointer] będzie równa 0.
Mam nadzieję, że dobrze wytłumaczyłem :)

Oto przykład błędnego działania i przykład programu dla którego kod źle działa:

Kopiuj
,>,>++++++++[<------<------>>-]<<[>[>+>+<<-]>>[<<+>>-]<<<-]>>>++++++[<++++++++>-]<.
Kopiuj
2
1
2
2
3
4
5
6
7
8
9
10
11
12
[12]
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
13

'[' jest na pozycji 12 natomiast ']' znajduje się na pozycji 30. Powyżej znajdują się wypisane wskaźniki. Wskaźnik po wykryciu ']' powinien wrócić na pozycję 12 zamiast 13 tak jak jest w przykładzie, aby program działał poprawnie.

No więc próbowałem zrobić coś takiego:

Kopiuj
 if bf_code[bf_code_pointer] == '[':
            tmp_bfstack.append(bf_code_pointer)
            print(tmp_bfstack)                # TEST
 if bf_code[bf_code_pointer] == ']' and bf_array[cell_pointer] != 0:
            bf_code_pointer = tmp_bfstack.pop() - 1

 bf_code_pointer += 1

bf_code_pointer = tmp_bfstack.pop() - 1 W tym przypadku wskaźnik rzeczywiście wskazuje na poprawną pozycję, ale program się zapętla.

Byłbym wdzięczny za jakąkolwiek pomoc w naprowadzeniu mnie na dobry tor


Guaz
  • Rejestracja:około 8 lat
  • Ostatnio:ponad 4 lata
  • Lokalizacja:Częstochowa
  • Postów:221
1

Przy zagnieżdżeniach przydaje się zawsze licznik które zagnieżdżenie powinien wykonywać, a które pamiętać :).
Tego nie unikniesz niestety, to jak z interpretacją znaczników w html'u kiedy się otwierają i kiedy zamykają :P.
Do tego trochę kodu musisz przebudować :)


Linux Mint
Arduino / Python 3.5.2
edytowany 1x, ostatnio: Guaz
Shizzer
Chodzi o zliczanie, który nawias jest pierwszy, który drugi itd.? Bo taki pomysł też miałem, ale porzuciłem go po drodze, ponieważ wydawało mi się, że ten przedstawiony zadziała poprawnie
Guaz
Nie chodzi o zliczanie który pierwszy który drugi, tylko ile zostało otworzonych, ile zamkniętych. A następnie identyfikator głębokości zagnieżdżenia. By pętla mogła mieć pętle w sobie :).
Guaz
To trochę jak python, zagnieżdżamy tabulacją/stałą_wartością_spacji. Z tą różnicą że w pythonie nie tylko pętle się zagnieżdża. Ale tą drogą warto iść na początku by działało :).
Shizzer
  • Rejestracja:prawie 8 lat
  • Ostatnio:5 miesięcy
  • Postów:231
0
Guaz napisał(a):

Przy zagnieżdżeniach przydaje się zawsze licznik które zagnieżdżenie powinien wykonywać, a które pamiętać :).
Tego nie unikniesz niestety, to jak z interpretacją znaczników w html'u kiedy się otwierają i kiedy zamykają :P.
Do tego trochę kodu musisz przebudować :)

Właśnie tutaj jest mój problem chyba, że nie mam pomysłu jak to zaimplementować. :) Myślałem, że moje rozwiązanie okaże się tym zliczaniem zagnieżdżeń. Bo zobacz, przypuśćmy, że ktoś chciałby dostać wynik działania takiego programu:

Kopiuj
>>+++[<<[+++--++<<>]-->]

Szczerze to nie wiem na ile to jest poprawnie w Brainfucku napisane, ten program ma posłużyć tylko jako przykład.
Próbowałem to zrobić właśnie w ten sposób, że

Kopiuj
if bf_code[bf_code_pointer] == '['

To dodaj pozycję do listy bf_stack. Czyli w liście bf_stack byłaby najpierw 5 w 0 elemencie, a następnie 8 w 1 elemencie, a wskaźnik przechodziłby dalej.

Kopiuj
if bf_code[bf_code_pointer] == ']' and bf_array[cell_pointer] != 0

Jeśli taki warunek byłby prawdziwy, czyli jakby wskaźnik doszedł do ']' to wtedy wskaźnik ma się równać bf_stack.pop(), czyli ustawiam wskaźnik na pozycję 8, a zatem wskaźnik wskazywałby na drugi w kolejności '[' i tak dalej by się to wykonywało. Jakby ta pętla doprowadziła do tego, że bf_array[cell_pointer] == 0
to wskaźnik by się przesunął i natrafiłby na ostatni prawy nawias. Znów zostałaby wykonana instrukcja bf_stack.pop(), a więc wskaźnik wskazywałby na pozycję 5, czyli na pierwszy w kolejności '['.

Myślałem, że ta metoda sama w sobie posiada licznik zagnieżdżeń. @Guaz mógłbyś mi powiedzieć jeszcze gdzie leży błąd w moim pomyśle? :) Nie bardzo rozumiem na czym to zliczanie miałoby polegać.


edytowany 1x, ostatnio: Shizzer
Guaz
  • Rejestracja:około 8 lat
  • Ostatnio:ponad 4 lata
  • Lokalizacja:Częstochowa
  • Postów:221
0

Pewnie, więc prześledźmy logikę wyrażenia. Zignorujmy póki co and, bo przyznam że nie wiem do czego służy - przez to mogę źle interpretować że to bezpiecznik, więc w takim wypadku przepraszam za zbyt daleko wysunięte wnioski.
I spróbujmy to narysować, dolna warstwa to program, kolejne to kierunek wykonywania i 'jumpy'.

Kopiuj
{
Napotykając '[' wrzuć pointer(nr znaku interpretowanego) do tmp_stack.
Napotykając ']' za pointer przyjmij ostatnią wartość z tmp_stack wyjmując ją.
}
      >>>>>>>>>>>>R?
      <=======
  >>>>H>>>>>>R
  <================
      >>>>>>>>>>>>R
      <=======
  H   H      R
>>[>>>[>>>>>>]....]..
H - zapis pozycji
R - powrót

Jeśli rozumiesz mój schemat, program finalnie wykona zagnieżdżoną dwukrotnie instrukcje 4 razy, zaś pełną 2 razy.
Więc brakuje nam tu jednego elementu. Decyzyjności, kiedy ma wyjść z tej pętli. Potrzebujesz dodatkowego sprawdzenia, kiedy ma wracać, a kiedy ma kontynuować działanie bo pętle już wykonał określoną ilość razy :).
Nie wiem jak ta decyzyjność w brainfuck'u przebiega, więc tutaj nie jestem w stanie pomóc, ze strony logicznej, tutaj widzę błąd.
''bf_code_pointer = tmp_bfstack.pop() - 1 W tym przypadku wskaźnik rzeczywiście wskazuje na poprawną pozycję, ale program się zapętla.''
Jeśli to pętla, to chyba dobrze żeby się zapętlał :D. Tylko kwestia zinterpretowania wyjścia z pętli. Więc powinna się tu znaleźć instrukcja if :).


Linux Mint
Arduino / Python 3.5.2
edytowany 1x, ostatnio: Guaz
Zobacz pozostały 1 komentarz
Guaz
Patrzyłem i nie mam pojęcia. Więc nic specjalnie nie mogę doradzić. A ten temat tyczy się czegoś innego.
Shizzer
Z tym and i z 0 chodzi o to, że pętla w Brainfucku zatrzymuje się w momencie gdy wskazywany przez wskaźnik element tablicy WARTOŚCI (nie kodu) jest równy właśnie 0 :) Czyli element tablicy wartości na których działa kod == 0 to wyjdź z pętli
Shizzer
I właśnie to 0 jest decyzyjnością. Ten pointer jest ustawiany na ostatnią dodaną pozycję '[' tylko wtedy gdy wartość w tablicy jest równa 0. Jeśli tak jest to pointer powinien zostać zinkrementowany i wyjść dzięki temu poza pętlę :/
bartek164
quazu to odpwiedz do mnie była czy do niego?
Guaz
@bartek164: Tak, ten komentarz pod twoim jest do ciebie.
Shizzer
  • Rejestracja:prawie 8 lat
  • Ostatnio:5 miesięcy
  • Postów:231
0
Guaz napisał(a):

Pewnie, więc prześledźmy logikę wyrażenia. Zignorujmy póki co and, bo przyznam że nie wiem do czego służy - przez to mogę źle interpretować że to bezpiecznik, więc w takim wypadku przepraszam za zbyt daleko wysunięte wnioski.
I spróbujmy to narysować, dolna warstwa to program, kolejne to kierunek wykonywania i 'jumpy'.

Kopiuj
{
Napotykając '[' wrzuć pointer(nr znaku interpretowanego) do tmp_stack.
Napotykając ']' za pointer przyjmij ostatnią wartość z tmp_stack wyjmując ją.
}
      >>>>>>>>>>>>R?
      <=======
  >>>>H>>>>>>R
  <================
      >>>>>>>>>>>>R
      <=======
  H   H      R
>>[>>>[>>>>>>]....]..
H - zapis pozycji
R - powrót

Jeśli rozumiesz mój schemat, program finalnie wykona zagnieżdżoną dwukrotnie instrukcje 4 razy, zaś pełną 2 razy.
Więc brakuje nam tu jednego elementu. Decyzyjności, kiedy ma wyjść z tej pętli. Potrzebujesz dodatkowego sprawdzenia, kiedy ma wracać, a kiedy ma kontynuować działanie bo pętle już wykonał określoną ilość razy :).
Nie wiem jak ta decyzyjność w brainfuck'u przebiega, więc tutaj nie jestem w stanie pomóc, ze strony logicznej, tutaj widzę błąd.
''bf_code_pointer = tmp_bfstack.pop() - 1 W tym przypadku wskaźnik rzeczywiście wskazuje na poprawną pozycję, ale program się zapętla.''
Jeśli to pętla, to chyba dobrze żeby się zapętlał :D. Tylko kwestia zinterpretowania wyjścia z pętli. Więc powinna się tu znaleźć instrukcja if :).

To jest dość ciekawe, bo program zapętla się tylko w jednym miejscu, Tak jakby reszta pętli przeszła w pełni poprawnie, a ta jedna miałaby w sobie błąd.

Kopiuj
46
47
48
49
50
51
52
53
-------------------------------------------
46
47
48
49
50
51
52
53
--------------------------------------------
46
47
48
49
50
51
52
53
--------------------------------------------
46
47
48
49
50
51
52
53
--------------------------------------------

Powyżej znajdują się pozycje wskaźników. Chodzi dokładnie o ten fragment programu w Brainfucku:

Kopiuj
[>[>+>+<<-]>>[<<+>>-]<<<-]

W tych zagnieżdżonych pętlach jest błąd, a konkretniej w drugiej z nich:

Kopiuj
[<<+>>-]

Lewy nawias jest na pozycji 46, a prawy na 53, a więc inkrementacja wskaźników działa poprawnie, ale ta inkrementacja niestety wykonuje się w nieskończoność, tak jakby ten wskazywany element tablicy nigdy nie mógł równać się 0.


Guaz
  • Rejestracja:około 8 lat
  • Ostatnio:ponad 4 lata
  • Lokalizacja:Częstochowa
  • Postów:221
1

@Shizzer: Twoje komentarze pod moją odpowiedzią, skłoniły mnie do szybkiego zapoznania się z brainfuck'iem, napisałem w sumie z palca prosty interpreter i działa nawet dla zagnieżdżeń. Więc pozwól że odpowiem po prostu kodem :)
[Równie szybko zainteresowanie przeszło, ale chwila zabawy była :D]

Kopiuj
class Interpreter:
    def __inc_ptr(self):  self.pointer+=1; self.cells.append(0) if len(self.cells)<=self.pointer else None;
    def __dec_ptr(self):  self.pointer-=1;
    def __inc_val(self):  self.cells[self.pointer]+=1;
    def __dec_val(self):  self.cells[self.pointer]-=1;
    def __put_char(self): print(chr(self.cells[self.pointer]));
    def __get_char(self): self.cells[self.pointer]=ord(input("wczytaj znak:"));
    def __beg_while(self):self.stack.append(self.pos);
    def __end_while(self):
        if self.cells[self.pointer] != 0:
            self.pos = self.stack[-1]
        else:
            self.stack.pop()

    def __init__(self):
        self.fct = {">": self.__inc_ptr,   \
                    "<": self.__dec_ptr,   \
                    "+": self.__inc_val,   \
                    "-": self.__dec_val,   \
                    ".": self.__put_char,  \
                    ",": self.__get_char,  \
                    "[": self.__beg_while, \
                    "]": self.__end_while  \
                    }


    def execute(self, code):
        self.cells = [0]
        self.pointer = 0
        self.stack = []
        self.pos = 0
        while self.pos < len(code):
            try:
                self.fct.get(code[self.pos])()
            except TypeError:
                pass #Pseudo zabezpieczenie przed niepożądanymi znakami typu spacje, opisy itd.
            finally:
                #~ print(code[self.pos], self.stack, self.pointer, self.cells) #Debug
                self.pos += 1

def main():
    a = Interpreter()
    a.execute(",>,>++++++++[<------<------>>-]<<[>[>+>+<<-]>>[<<+>>-]<<<-]>>>++++++[<++++++++>-]<.")
    a.execute(",>,< [ > [ >+ >+ << -] >> [- << + >>] <<< -] >>")

if __name__ == "__main__":
    main()

Z klasą to może lekki przerost formy nad treścią, ale potrzebowałem parametru self do funkcji :).
Jakoś taka pętla z referencjami do funkcji wydaje mi się czytelniejsza :p.

Ale przechodząc do konkretu, miałeś rację co do fragmentu w którym się myliłeś, tylko zabrakło ci jednej instrukcji.
Jeśli nasz znak w kodzie jest ']' i aktualnie wskazywana komórka jest zerem, to wtedy musi iść dalej, olewając ten znak (wyjście z pętli) i usuwając ostatnio odłożony indeks.
W przeciwnym wypadku ma pobrać ostatnio odłożoną wartość nawiasu ale nie kasując jej jak to robisz za pomocą .pop.

I jak w sumie nadmieniałem (ale na ślepo strzelając) .pop()+1 jest błędem. Wystarczy pozbawić linię +1, oraz zrobić poprawne wyjście z pętli.
Wtedy napotykając znak '[' do którego się przeniesiemy, znów go dodamy. (chociaż to sztuka dla sztuki, ale myślenie dobre)

@Edit:
Mała poprawka kodu na poprawnie napisany.

@Edit2:
Dla szybkości polecam sprawdzić czy szybszy jest try/sprawdzenie czy znak znajduje się w zakresie dozwolonych/pozostawienie u znaków dozwolonych przed pętlą wykonującą.
Wydaje mi się że ostatnia opcja:
code = "".join(i for i in code if i in self.fct)


Linux Mint
Arduino / Python 3.5.2
edytowany 4x, ostatnio: Guaz
Guaz
Jeszcze jeden szczegół, do implementacji. jako że było późno to jakoś nie zauważyłem. Po wypisanym znaku, nie powinno być nowej linii, dopóki nie przejdziemy do nowego wskaźnika który nam ją narzuci. Jak w przykładzie z wikipedii polskiej: ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>. konkretniej ostatnie >.
Shizzer
Czyli ogólnie nie dużo mi brakło do poprawnego działania programu co w sumie mnie cieszy. Dziękuję Ci bardzo za pomoc. Teraz będzie ciekawiej, bo to samo muszę przełożyć na Assembly czego wymaga ode mnie treść ćwiczenia w książce :)
Guaz
No więc powodzenia :D. Rozwiązanie było owszem bardzo blisko ^^.
Shizzer
Dzięki, na pewno się przyda :D
Shizzer
Sprawdzałeś może czy interpreter działa poprawnie jeśli chodzi o program z dzieleniem dwóch wpisanych liczb przez użytkownika? Ten program jest na wiki: https://pl.wikipedia.org/wiki/Brainfuck#Dzielenie Bo mi się na dzieleniu zapętla, a mnożenie wykonuje poprawnie. Być może ja gdzieś jeszcze popełniłem błąd, dlatego pytam :)
Guaz
  • Rejestracja:około 8 lat
  • Ostatnio:ponad 4 lata
  • Lokalizacja:Częstochowa
  • Postów:221
0

Tak, ale to jest albo błąd definicji, albo niedoprecyzowanie.
Zauważ że wczytujemy liczbę jako znak ascii o określonym numerze.
Jeśli cyfry powinny oznaczać co reprezentują, musimy zmienić funkcję: def __put_char(self): print(chr(self.cells[self.pointer]), end="");
W innym wypadku powinno nam zwrócić znak reprezentowany przez liczbę w ascii 2 (bo spacja dzieli z resztą dwa razy "A").

Kopiuj
wczytaj znak:A
, [] 0 [65]
> [] 1 [65, 0]
wczytaj znak: #<= tu jest spacja
, [] 1 [65, 32]
> [] 2 [65, 32, 0]
+ [] 2 [65, 32, 1]
+ [] 2 [65, 32, 2]
+ [] 2 [65, 32, 3]
+ [] 2 [65, 32, 4]
+ [] 2 [65, 32, 5]
+ [] 2 [65, 32, 6]
[ [10] 2 [65, 32, 6]
- [10] 2 [65, 32, 5]
< [10] 1 [65, 32, 5]
- [10] 1 [65, 31, 5]
- [10] 1 [65, 30, 5]
- [10] 1 [65, 29, 5]
- [10] 1 [65, 28, 5]
- [10] 1 [65, 27, 5]
- [10] 1 [65, 26, 5]
- [10] 1 [65, 25, 5]
- [10] 1 [65, 24, 5]
< [10] 0 [65, 24, 5]
- [10] 0 [64, 24, 5]
- [10] 0 [63, 24, 5]
- [10] 0 [62, 24, 5]
- [10] 0 [61, 24, 5]
- [10] 0 [60, 24, 5]
- [10] 0 [59, 24, 5]
- [10] 0 [58, 24, 5]
- [10] 0 [57, 24, 5]
> [10] 1 [57, 24, 5]
> [10] 2 [57, 24, 5]
[ [10] 2 [57, 24, 5]
- [10] 2 [57, 24, 4]
< [10] 1 [57, 24, 4]
- [10] 1 [57, 23, 4]
- [10] 1 [57, 22, 4]
- [10] 1 [57, 21, 4]
- [10] 1 [57, 20, 4]
- [10] 1 [57, 19, 4]
- [10] 1 [57, 18, 4]
- [10] 1 [57, 17, 4]
- [10] 1 [57, 16, 4]
< [10] 0 [57, 16, 4]
- [10] 0 [56, 16, 4]
- [10] 0 [55, 16, 4]
- [10] 0 [54, 16, 4]
- [10] 0 [53, 16, 4]
(...)
< [10] 0 [41, 0, 2]
- [10] 0 [40, 0, 2]
- [10] 0 [39, 0, 2]
- [10] 0 [38, 0, 2]
- [10] 0 [37, 0, 2]
- [10] 0 [36, 0, 2]
- [10] 0 [35, 0, 2]
- [10] 0 [34, 0, 2]
- [10] 0 [33, 0, 2]
> [10] 1 [33, 0, 2]
> [10] 2 [33, 0, 2]
[ [10] 2 [33, 0, 2]
- [10] 2 [33, 0, 1]
< [10] 1 [33, 0, 1]
- [10] 1 [33, -1, 1]
- [10] 1 [33, -2, 1]
- [10] 1 [33, -3, 1]

Natomiast patrząc na to jak się wykonuje kod, Nasza pętla pomimo wskaźnika równego 0, zaczyna dekrementację i będzie dekrementować w nieskończoność wykonując jeszcze kilka operacji pobocznych...
Jedyne co pozostaje, to sprawdzić jak się wykonuje kod w prawdziwym interpreterze. Bo według logiki powinna zwrócić znak o numerze 2 i kod jest spaprany o czym świadczy dekrementacja poniżej zakresu. Ale sprawdzę to w najbliższych dniach lub tej nocy, aktualnie już nie mam czasu by podłubać :).


Linux Mint
Arduino / Python 3.5.2
edytowany 1x, ostatnio: Guaz
Guaz
Ogarnąłem błąd w mojej logice wczytując się w poprzednie funkcje, wrzucę w najbliższym czasie poprawkę - jest błąd w tym interpreterze do działań arytmetycznych, polegający na kopiowaniu wartości ;D
Shizzer
Ja też spróbuję jutro coś pomyśleć o tym, ale spokojnie możesz mnie uprzedzić ;)
Shizzer
  • Rejestracja:prawie 8 lat
  • Ostatnio:5 miesięcy
  • Postów:231
0

Na razie poprawiłem kod uwzględniając w nim wyjście poza zakres bitów reprezentujących znaki ascii w operacjach arytmetycznych:

Kopiuj
#!/usr/bin/python3

import sys


def execute(filename):
    f = open(filename, 'r')
    translate(f.read())
    f.close()


def translate(commands):

    bf_array, cell_pointer, bf_code_pointer = [0], 0, 0
    bf_code = list(commands)
    tmp_stack = []        # pozycje nawiasow

    while bf_code_pointer < len(bf_code):

        if bf_code[bf_code_pointer] == '>':
            cell_pointer += 1
            if cell_pointer == len(bf_array): bf_array.append(0)

        if bf_code[bf_code_pointer] == '<':
            if cell_pointer <= 0:
                cell_pointer = 0
            else:
                cell_pointer -= 1

        if bf_code[bf_code_pointer] == '+':
           if bf_array[cell_pointer] < 255:
                bf_array[cell_pointer] += 1
           else:
               bf_array[cell_pointer] = 0

        if bf_code[bf_code_pointer] == '-':
            if bf_array[cell_pointer] > 0:
                bf_array[cell_pointer] -= 1
            else:
                bf_array[cell_pointer] = 255

        if bf_code[bf_code_pointer] == '.': print(chr(bf_array[cell_pointer]))

        if bf_code[bf_code_pointer] == ',': bf_array[cell_pointer] = ord(input('Wczytaj znak: '))

        if bf_code[bf_code_pointer] == '[':
            tmp_stack.append(bf_code_pointer)

        if bf_code[bf_code_pointer] == ']' and bf_array[cell_pointer] != 0:
            bf_code_pointer = tmp_stack[-1]

        if bf_code[bf_code_pointer] == ']' and bf_array[cell_pointer] == 0:
            tmp_stack.pop()

        bf_code_pointer += 1


def main():
    if len(sys.argv) == 2: execute(sys.argv[1])
    else: print('Try:', sys.argv[0], 'filename.')


main()

Natomiast nadal występuje problem zapętlania.

Kopiuj
[3, 0, 136, 0, 121, 0, 0]
[3, 0, 136, 0, 121, 0, 0]
[3, 0, 136, 0, 121, 0, 0]
[3, 0, 136, 0, 121, 0, 0]
[3, 0, 136, 0, 120, 0, 0]
[3, 0, 136, 0, 120, 0, 0]
[3, 0, 136, 0, 120, 0, 0]
[3, 0, 137, 0, 120, 0, 0]
[3, 0, 137, 0, 120, 0, 0]
[3, 0, 137, 0, 120, 0, 0]
[3, 0, 137, 0, 120, 0, 0]
[3, 0, 137, 0, 119, 0, 0]
[3, 0, 137, 0, 119, 0, 0]
[3, 0, 137, 0, 119, 0, 0]
[3, 0, 138, 0, 119, 0, 0]
[3, 0, 138, 0, 119, 0, 0]
[3, 0, 138, 0, 119, 0, 0]
[3, 0, 138, 0, 119, 0, 0]
[3, 0, 138, 0, 118, 0, 0]
[3, 0, 138, 0, 118, 0, 0]
[3, 0, 138, 0, 118, 0, 0]
[3, 0, 139, 0, 118, 0, 0]
[3, 0, 139, 0, 118, 0, 0]
[3, 0, 139, 0, 118, 0, 0]
[3, 0, 139, 0, 118, 0, 0]
[3, 0, 139, 0, 117, 0, 0]
[3, 0, 139, 0, 117, 0, 0]
[3, 0, 139, 0, 117, 0, 0]
[3, 0, 140, 0, 117, 0, 0]
[3, 0, 140, 0, 117, 0, 0]
[3, 0, 140, 0, 117, 0, 0]
[3, 0, 140, 0, 117, 0, 0]
[3, 0, 140, 0, 116, 0, 0]
[3, 0, 140, 0, 116, 0, 0]
[3, 0, 140, 0, 116, 0, 0]
[3, 0, 141, 0, 116, 0, 0]
[3, 0, 141, 0, 116, 0, 0]
[3, 0, 141, 0, 116, 0, 0]
[3, 0, 141, 0, 116, 0, 0]
[3, 0, 141, 0, 115, 0, 0]
[3, 0, 141, 0, 115, 0, 0]
[3, 0, 141, 0, 115, 0, 0]
[3, 0, 142, 0, 115, 0, 0]
[3, 0, 142, 0, 115, 0, 0]
[3, 0, 142, 0, 115, 0, 0]
[3, 0, 142, 0, 115, 0, 0]
[3, 0, 142, 0, 114, 0, 0]
[3, 0, 142, 0, 114, 0, 0]
[3, 0, 142, 0, 114, 0, 0]
[3, 0, 143, 0, 114, 0, 0]
[3, 0, 143, 0, 114, 0, 0]
[3, 0, 143, 0, 114, 0, 0]
[3, 0, 143, 0, 114, 0, 0]
[3, 0, 143, 0, 113, 0, 0]
[3, 0, 143, 0, 113, 0, 0]
[3, 0, 143, 0, 113, 0, 0]
[3, 0, 144, 0, 113, 0, 0]
[3, 0, 144, 0, 113, 0, 0]
(...)

Przy cyfrach 4 i 2, które po podzieleniu oczywiście powinny dać cyfrę 2 wypisują na ekran 0 i to 0 jest wypisaywane dla każdej innej pary liczb


edytowany 1x, ostatnio: Shizzer
Guaz
  • Rejestracja:około 8 lat
  • Ostatnio:ponad 4 lata
  • Lokalizacja:Częstochowa
  • Postów:221
1

Zerknąłem już jakiś czas temu na opis kodu i widzę że w ogóle źle rozumiałem, to miało tylko dzielić liczby :P.
>[-<<- odejmujemy 1 od dzielnej (0) i dzielnika (2) dopóki (2) nie jest 0
W tym miejscu wysypuje się interpreter. I jest to błąd ponieważ zamiast w pętli odejmować od dzielnej dzielnika, przerzuca nam się na inne zagnieżdżenie. przez co przeskakują nam nieprawidłowo wskaźniki.
Ale jak to poprawić, to spróbuję na tygodniu, to wskazówka jeśli się będziesz z tym bawił w międzyczasie :)

@Edit:
",>,>++++++[-<--------<-------->>]<<[>[>+>+<<-]>[-<<->>]>>+<[-<<+>>]<<<]>>>++++++[->++++++++<]>."
A ten kod działa dla dzielenia całkowitego (9 i 3, oraz 4 i 2) z zabezpieczeniem dla wartości ujemnych, powinno też dla innych wartości :).
Z tym że wyjdzie zapewne wtedy 9/2 == 5 zaokrąglane w górę.
Ale to problem był z kodem z wiki, nie interpreterem tak jak sądziłem wyżej.


Linux Mint
Arduino / Python 3.5.2
edytowany 3x, ostatnio: Guaz
Shizzer
  • Rejestracja:prawie 8 lat
  • Ostatnio:5 miesięcy
  • Postów:231
0
Guaz napisał(a):

Zerknąłem już jakiś czas temu na opis kodu i widzę że w ogóle źle rozumiałem, to miało tylko dzielić liczby :P.
>[-<<- odejmujemy 1 od dzielnej (0) i dzielnika (2) dopóki (2) nie jest 0
W tym miejscu wysypuje się interpreter. I jest to błąd ponieważ zamiast w pętli odejmować od dzielnej dzielnika, przerzuca nam się na inne zagnieżdżenie. przez co przeskakują nam nieprawidłowo wskaźniki.
Ale jak to poprawić, to spróbuję na tygodniu, to wskazówka jeśli się będziesz z tym bawił w międzyczasie :)

@Edit:
",>,>++++++[-<--------<-------->>]<<[>[>+>+<<-]>[-<<->>]>>+<[-<<+>>]<<<]>>>++++++[->++++++++<]>."
A ten kod działa dla dzielenia całkowitego (9 i 3, oraz 4 i 2) z zabezpieczeniem dla wartości ujemnych, powinno też dla innych wartości :).
Z tym że wyjdzie zapewne wtedy 9/2 == 5 zaokrąglane w górę.
Ale to problem był z kodem z wiki, nie interpreterem tak jak sądziłem wyżej.

Wszystko ładnie działa. Sprawdzałem kody Brainfucka wprowadzając dane do mojego interpretera i do interpretera online - wyniki są takie same.

Dzięki wielkie za pomoc :)

Jeszcze bardzo by było miło jakbyś napisał czy taki kod jest wystarczający czy można go jakoś ładniej napisać:

Kopiuj
#!/usr/bin/python3

import sys


def execute(filename):
    f = open(filename, 'r')
    translate(f.read())
    f.close()


def translate(commands):

    bf_array, cell_pointer, bf_code_pointer = [0], 0, 0
    bf_code = list(commands)
    tmp_stack = []        # pozycje nawiasow

    while bf_code_pointer < len(bf_code):

        if bf_code[bf_code_pointer] == '>':
            cell_pointer += 1
            if cell_pointer == len(bf_array): bf_array.append(0)

        if bf_code[bf_code_pointer] == '<':
            if cell_pointer <= 0:
                cell_pointer = 0
            else:
                cell_pointer -= 1

        if bf_code[bf_code_pointer] == '+':
           if bf_array[cell_pointer] < 255:
                bf_array[cell_pointer] += 1
           else:
               bf_array[cell_pointer] = 0

        if bf_code[bf_code_pointer] == '-':
            if bf_array[cell_pointer] > 0:
                bf_array[cell_pointer] -= 1
            else:
                bf_array[cell_pointer] = 255

        if bf_code[bf_code_pointer] == '.': print(chr(bf_array[cell_pointer]))

        if bf_code[bf_code_pointer] == ',': bf_array[cell_pointer] = ord(input('Wczytaj znak: '))

        if bf_code[bf_code_pointer] == '[':
            tmp_stack.append(bf_code_pointer)

        if bf_code[bf_code_pointer] == ']' and bf_array[cell_pointer] != 0:
            bf_code_pointer = tmp_stack[-1]

        if bf_code[bf_code_pointer] == ']' and bf_array[cell_pointer] == 0:
            tmp_stack.pop()

        bf_code_pointer += 1


def main():
    if len(sys.argv) == 2: execute(sys.argv[1])
    else: print('Try:', sys.argv[0], 'filename.')


main()

Zależy mi na tym, żeby kod był jak najlepszy, ale jeszcze mało wiem na temat 'czystego kodu'. Wiem, że jest książka na ten temat i zapewne niedługo trafi na moją półkę :)


edytowany 1x, ostatnio: Shizzer
Guaz
Co do kodu, polecam jak masz if, elif i else (a instrukcje są jednolinijkowe jak u ciebie w main) rozpocząć od takiej samej tabulacji. I w sumie tyle, reszta jest czytelna. Niemniej, do wyboru odpowiedniego znaku, zamiast kolumny if powinieneś użyć słownika to bardziej pythonowe. A jak już używasz ifów, które się nie pokrywają (żaden argument nie spełni dwóch jednocześnie) to powinieneś używać elif'a aby pominąć sprawdzanie następnych po tym który był prawdziwy :). To akurat optymalizacyjne :P
Guaz
Ah, no i jednolitość stylu, w if bf_code[bf_code_pointer] == ',': bf_array[cell_pointer] = ord(input('Wczytaj znak: ')) tu dajesz po if w jednej linii, a w następnym argumencie już dajesz operację jedną linię niżej. Tyle mogę zarzucić, nic więcej nie widzę :)
Shizzer
Dzięki wielkie raz jeszcze
Shizzer
  • Rejestracja:prawie 8 lat
  • Ostatnio:5 miesięcy
  • Postów:231
0

Kiedyś miałem problem odnośnie resetowania wartości zmiennych gdy działam na funkcjach. Chciałem użyć słowników zamiast tych if'ów właśnie i niestety, ale znów nastąpił problem w kontekście resetowania wartości. Nie umiem jeszcze programowania objektowego, a nie chcę kopiować Twojego kodu, ale wiem, że Ty mogłeś zastosować słowniki i nie miałeś problemów z resetowaniem wartości zmiennych, bo miałeś do dyspozycji self. Jak mógłbym zastosować słowniki przy kodzie opartym na funkcjach?

Kopiuj
#!/usr/bin/python3

import sys


def execute(filename):
    f = open(filename, 'r')
    translate(f.read())
    f.close()


def translate(commands):

    cells, cell_pointer, code_pointer = [0], 0, 0
    bf_code = list(commands)
    stack = []

    def _inc_ptr(cell_pointer, cells):
        cell_pointer += 1
        if cell_pointer == len(cells):
            cells.append(0)

    def _dec_ptr(cell_pointer, cells):
        cell_pointer -= 1
        if cell_pointer <= 0:
            cell_pointer = 0

    def _increment_value(cells, cell_pointer):
        cells[cell_pointer] = 0
        if cells[cell_pointer] < 255:
            cells[cell_pointer] += 1

    def _decrement_value(cells, cell_pointer):
        cells[cell_pointer] = 0
        if cells[cell_pointer] > 0:
            cells[cell_pointer] -= 1

    def _put_char(cells, cell_pointer):
        print(chr(cells[cell_pointer]))

    def _get_char(cells, cell_pointer):
        cells[cell_pointer] = ord(input("Write a sign: "))

    def _begin_loop(stack, code_pointer):
        stack.append(code_pointer)

    def _end_loop(code_pointer, stack, cells, cell_pointer):
        if cells[cell_pointer] != 0:
            code_pointer = stack[-1]
        else:
            stack.pop()

    signs = {   ">": _inc_ptr(cell_pointer, cells),
                "<": _dec_ptr(cell_pointer, cells),
                "+": _increment_value(cells, cell_pointer),
                "-": _decrement_value(cells, cell_pointer),
                ".": _put_char(cells, cell_pointer),
                ",": _get_char(cells, cell_pointer),
                "[": _begin_loop(stack, code_pointer),
                "]": _end_loop(code_pointer, stack, cells, cell_pointer)
            }

    while code_pointer < len(bf_code):
        try:
            signs.get(bf_code[code_pointer])
        finally:
            code_pointer += 1


def main():
        if len(sys.argv) == 2:
            execute(sys.argv[1])
        else:
            print('Try:', sys.argv[0], 'filename.')


main()


Shizzer
  • Rejestracja:prawie 8 lat
  • Ostatnio:5 miesięcy
  • Postów:231
0

Być może ten post pomoże kiedyś komuś kto też będzie szukał rozwiązania podobnego problemu :)

Problem inkrementowania zmiennych globalnych przy użyciu funkcji został rozwiązany i był dość trywialny, aż wstyd, że dopiero teraz wpadł mi do głowy. :)
Mianowicie zmienne globalne nie inkrementowały się poprawnie, ponieważ w funkcjach rzecz jasna działamy na kopiach zmiennych, a nie na ich oryginalnych postaciach. W pythonie istnieje sposób na zaimplementowanie działania w funkcji na oryginałach zmiennych globalnych, a służy do tego global.

Oto przykład:

Kopiuj
  count = 0
   def counter(): 
..     global count 
..     count += 1 
..     return count 
..     
   counter()
=> 1
   counter()
=> 2
   counter()
=> 3

Jak widać można działać na oryginałach zmiennych nawet wewnątrz funkcji.


Guaz
Nie zauważyłem że już sobie sam odpowiedziałeś :D. Anyway, może komuś się przydadzą poniższe wymienione możliwości :).
Guaz
  • Rejestracja:około 8 lat
  • Ostatnio:ponad 4 lata
  • Lokalizacja:Częstochowa
  • Postów:221
1

Jest kilka sposobów:
#1 Argumenty domyślnie przekazywane do funkcji w słowniku przy wywołaniu go metodą get (tylko musisz wtedy zastosować lambdę, aby to było głębokie wywołanie, inaczej ci się wywołają w trakcie inicjalizacji słownika gdy dasz mu nawiasy wywołujące, tu jest jednak zagadka jak przypisać to do odpowiedniej zmiennej lokalnej w funkcji rodzicielskiej. w tym powinna ci pomóc ''globalność'' słowników.
https://stackoverflow.com/questions/14323817/global-dictionaries-dont-need-keyword-global-to-modify-them
I ta opcja jest zdecydowanie najlepsza, bo nie wykorzystujesz globalnych nazw gdybyś zaimportował ten słownik w jakimś programie przypadkiem poprzez instrukcję from plik import *
A przy okazji wszystkie zmienne masz zmagazynowane według identyfikatorów w jednym zbiorze, bo nadal jest to integralna część funkcji powstająca zawsze.
#2 Druga opcja, to zmienne globalne i jak wyżej, tylko bez przekazywania argumentów domyślnych. global jest jednak odradzaną przeze mnie metodą, powyższa robi pseudoglobalną.
#3 Jest jeszcze trzecia opcja, w której wykorzystujesz metodę __call__, to jest jednak już nieco bardziej zakrawające o obiektowość.


Linux Mint
Arduino / Python 3.5.2
edytowany 1x, ostatnio: Guaz
Guaz
  • Rejestracja:około 8 lat
  • Ostatnio:ponad 4 lata
  • Lokalizacja:Częstochowa
  • Postów:221
0

Dla ciekawskich pseudoglobalności, czy raczej 'referencji' słowników na tym przykładzie, przy programowaniu funkcyjnym:

Kopiuj
class Interpreter:
    def __inc_ptr(self):  self.pointer+=1; self.cells.append(0) if len(self.cells)<=self.pointer else None;
    def __dec_ptr(self):  self.pointer-=1;
    def __inc_val(self):  self.cells[self.pointer]+=1;
    def __dec_val(self):  self.cells[self.pointer]-=1;
    def __put_char(self): print(chr(self.cells[self.pointer]), end="");
    def __get_char(self): self.cells[self.pointer]=ord(input("wczytaj znak:"));
    def __beg_while(self):self.stack.append(self.pos);
    def __end_while(self):
        if self.cells[self.pointer] > 0:
            self.pos = self.stack[-1]
        else:
            self.stack.pop()

    def __init__(self):
        self.fct = {">": self.__inc_ptr,   \
                    "<": self.__dec_ptr,   \
                    "+": self.__inc_val,   \
                    "-": self.__dec_val,   \
                    ".": self.__put_char,  \
                    ",": self.__get_char,  \
                    "[": self.__beg_while, \
                    "]": self.__end_while  \
                    }


    def execute(self, code):
        self.cells = [0]
        self.pointer = 0
        self.stack = []
        self.pos = 0
        code = "".join(i for i in code if i in self.fct)
        while self.pos < len(code):
            self.fct.get(code[self.pos])()
            self.pos += 1

def interpreter(code):
    def __inc_ptr(D):  D["pointer"] += 1; D["cells"].append(0) if len(D["cells"]) <= D["pointer"] else None;
    def __dec_ptr(D):  D["pointer"] -= 1;
    def __inc_val(D):  D["cells"][D["pointer"]]+=1;
    def __dec_val(D):  D["cells"][D["pointer"]]-=1;
    def __put_char(D): print(chr(D["cells"][D["pointer"]]), end="");
    def __get_char(D): D["cells"][D["pointer"]]=ord(input("wczytaj znak:"));
    def __beg_while(D): D["stack"].append(D["pos"]);
    def __end_while(D):
        if D["cells"][D["pointer"]] > 0:
            D["pos"] = D["stack"][-1]
        else:
            D["stack"].pop()

    data = {"pointer": 0,
            "cells": [0],
            "stack": [],
            "pos": 0
            }

    signs = {   ">": __inc_ptr,
                "<": __dec_ptr,
                "+": __inc_val,
                "-": __dec_val,
                ".": __put_char,
                ",": __get_char,
                "[": __beg_while,
                "]": __end_while
            }

    code = "".join(i for i in code if i in signs)
    while data["pos"] < len(code):
        signs.get(code[data["pos"]])(data)
        data["pos"] += 1


def main():
    while True:
        interpreter(",>,>++++++[-<--------<-------->>]<<[>[>+>+<<-]>[-<<->>]>>+<[-<<+>>]<<<]>>>++++++[->++++++++<]>.")
        a = Interpreter()
        a.execute(",>,>++++++[-<--------<-------->>]<<[>[>+>+<<-]>[-<<->>]>>+<[-<<+>>]<<<]>>>++++++[->++++++++<]>.")

if __name__ == "__main__":
    main()

Czytelność kodu wzrasta. ale prędkość i sens nie używania obiektowości, niestety spadają ;p.


Linux Mint
Arduino / Python 3.5.2

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.