Яндекс.Метрика

    Песочница

    Prolog. Программируем автоматы

    Прочитав статью о Prolog, я решил написать небольшое дополнение к ней в виде 2 небольших задач.
    Вот они:
    1. Интерпретатор языка brainfuck
    2. Машина Тьюринга
    Для начала нам требуется SWI-Prolog и немного свободного времени.

    Задача 1. Интерпритатор языка brainfuck

    Начнем с копи-паста из Википедии.
    Brainfuck — один из известнейших эзотерических языков программирования, придуман Урбаном Мюллером в 1993 году для забавы. Язык имеет восемь команд, каждая из которых записывается одним символом. Исходный код программы на Brainfuck представляет собой последовательность этих символов без какого-либо дополнительного синтаксиса.
    Машина, которой управляют команды Brainfuck, состоит из упорядоченного набора ячеек и указателя текущей ячейки, напоминая ленту и головку машины Тьюринга. Кроме того, подразумевается устройство общения с внешним миром (см. команды. и ,) через поток ввода и поток вывода.

    Команды и их описание:

    • > перейти к следующей ячейке
    • < перейти к предыдущей ячейке
    • + увеличить значение в текущей ячейке на 1
    • — уменьшить значение в текущей ячейке на 1
    • . напечатать значение из текущей ячейки
    • , ввести извне значение и сохранить в текущей ячейке
    • [ если значение текущей ячейки нуль, перейти вперёд по тексту программы на ячейку, следующую за соответствующей ] (с учётом вложенности)
    • ] если значение текущей ячейки не нуль, перейти назад по тексту программы на символ [ (с учётом вложенности)


    Лента представленна в виде базы данных
    data(Адресс, Значение).
    Головка представлена адрессом ячейки
    pos(Адресс).

    Собственно код с комментариями:
    % ,
    cout:-
    %получаем адресс текщей ячейки
    pos(Addr),
    %получаем значение
    data(Addr,Value),
    %выводим его на экран
    put_char([Value]).
    % .
    cin:-
    %получаем адресс текущей ячейки
    pos(Addr),
    %удаляем ячейку из базы
    retract(data(Addr,_)),
    %читаем символ
    get_single_char(Value),
    %записываем назад в базу
    assert(data(Addr,Value)).
    % + -
    add(AddValue):-
    %получаем адресс
    pos(Addr),
    %удаляем ячейку
    retract(data(Addr,Value)),
    %увеличиваем значение на 1
    NewValue is Value+AddValue,
    %заносим новую ячейку
    assert(data(Addr,NewValue)).
    % > <
    addr(Side):-
    %удаляем текущий адресс
    retract(pos(Addr)),
    %инкрементриуем значение адресса
    NewAddr is Addr+Side,
    %заносим новое значение в базу
    assert(pos(NewAddr)),
    %ячейка была в базу или создаем новую
    (data(NewAddr,_),!;assert(data(NewAddr,0))).

    % ]
    goto:-
    %получаем адресс
    pos(Addr),
    %получаем значение и проверяем на равность 0
    data(Addr,Value),Value==0,!,
    %если 0, то удаляем указатель на начало последнего цикла
    retract(circle([_|L])),
    %сохраняем хвост
    assert(circle(L)).
    goto:-
    %иначе переходим в начало цикла
    circle([[N,_]|_]),
    seeing(Stream),
    seek(Stream,N,bof,_).
    % [
    loop:-
    %удаляем и получаем указатели на начало цикла
    retract(circle(L)),
    seeing(Stream),
    %определяем позицию в файле
    character_count(Stream,Pos),
    %Получаем значение ячейки
    %(если оно = 0, то не выполняем тело цикла)
    pos(Addr),
    data(Addr,Value),
    assert(circle([[Pos,Value]|L])).

    do:-
    %читаем команду
    get_char(X),
    %выполняем ее
    step(X),
    %проверяем на конец файла
    not(at_end_of_stream),!,
    do.
    do:-
    %если конец, то закрываем файл
    seeing(Stream),
    close(Stream),
    seen.

    step('['):-loop,!.
    step(']'):-goto,!.
    %это правило предназначено для "проскакивания цикла"
    %(если в него мы зашли с нулевым значением)
    step(_):-circle([[_,StartValue]|_]),StartValue==0,!.
    step('>'):-addr( 1),!.
    step('<'):-addr(-1),!.
    step(','):-cin,!.
    step('.'):-cout,!.
    step('+'):-add( 1),!.
    step('-'):-add(-1),!.
    step(_).

    run(Path):-
    see(Path),
    %удаляем мусор (оставленный после работы предидущей программы)
    retractall(pos(_)),
    retractall(data(_,_)),
    retractall(circle(_)),
    %начинаем с первой ячейки
    assert(pos(1)),
    %с значением 0
    assert(data(1,0)),
    assert(circle([])),do.


    Для проверки в терминале пишем:

    freest@PC:$ swipl -s <path.tofile>
    ?-run('Path.to.Brainfuck.code').

    Протеструем на примере из Вики.

    Hello World! на brainfuck

    ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++
    .>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.
    ------.--------.>+.>.

    Должно выйти что-то вроде этого:

    freest@PC:/media/C6984667984655D9$ swipl -s bf.pro
    % library(swi_hooks) compiled into pce_swi_hooks 0.00 sec, 2,224 bytes
    % /media/C6984667984655D9/bf.pro compiled 0.00 sec, 4,676 bytes
    Welcome to SWI-Prolog (Multi-threaded, 32 bits, Version 5.10.4)
    Copyright © 1990-2011 University of Amsterdam, VU Amsterdam
    SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software,
    and you are welcome to redistribute it under certain conditions.
    Please visit www.swi-prolog.org for details.

    For help, use ?- help(Topic). or ?- apropos(Word).

    ?- run('a.txt').
    Hello World!
    true.

    ?-

    Задача 2. Машина Тьюринга


    Опять немного теории от Вики
    Маши