Pull to refresh

Пишем интерпретатор для своего эзотерического языка (часть 2)

Reading time 5 min
Views 1.7K
Напомню, что в предыдущей статье я поставил задачу написать интерпретатор для надстройки над Brainfuck. Естественно, что для начала нужно было реализовать сам Brainfuck, и только затем переходить к надстройке. Благо в предыдущей статье эта часть была реализована. Собственно опишем то, что следует реализовать в этой части:
  1. Понятие функции(процедуры).
  2. Комментарии.


Комментарии

    Это самая легкая часть. Комментарием будем считать всякую последовательность символов заключенных в круглые скобки. Заметим, что скобочная последовательность «обрамляющая» текст комментария обязательно должна быть правильной. Это очевидно. Вот собственно и все. И еще: комментарии должны «отсеиваться» в начале парсинга — в конце должны получить только код, который надо выполнить. Это делается просто:

unsigned int skipComment( std::ifstream &file )
{
    unsigned int skipped = 0;
    unsigned int unclosedComments = 1;
    while( file.good() && ( unclosedComments != 0 ) )
    {
        char tmp = (char) file.get();
        if ( bf_cBeg == tmp )
            unclosedComments++;
        else if ( tmp == bf_cEnd )
            unclosedComments--;
        skipped++;
    }
    return skipped;
}


Функция вызовется сразу как встретится символ '('. Она «проглотит» комментарий и вернет количество проглоченных символов. Все, вопрос решен. Перейдем к следующей задаче.

Функции/Процедуры.

Тут, конечно, посложнее будет!
Потребуем:
  • Идентификатор(имя) функции должен быть уникальным, Далее видно будет откуда это требование появилось.
  • 2. Функция возвращает только одно значение, и оно(значение) совпадает со значением нулевого регистра.
  • 3. Для передачи параметров в функцию будем использовать копию текущего состояния регистров.

Итак тело функции должно иметь вид:
{%<имя>%<код>}

Именем может служить любая последовательность символов без открывающей круглой скобки.

Для поиска имени функции используем функцию:

std::string parseName( std::ifstream &file, const char parsingAfter )
{
    if ( file.bad() || ( bf_fNme != parsingAfter ) )
        return std::string();

    std::string res = std::string();

    while ( file.good() )
    {
        char tmp = (char) file.get();
        if ( tmp == bf_fNme )
            return res;
        res += tmp;

    }

    return res;
}


Эта функция нам будет полезна, когда нужно будет определить имя функции, чтоб ее вызвать. Для хранения функции будем использовать структуру:

struct proc
{
    char code[ maxCodeSize ]; // код функции
    unsigned int realCodeSize; // размер кода
};

И будем хранить функции в std::map< std::string, proc > procedures, как ключ будем использовать файл и наполнять идентификатор функции. Осталось научиться парсить файл и наполнять контейнер procedures. Вот:

bool parseFunction( std::ifstream &file, const char parsingAfter )
{
    if( ( bf_fBeg != parsingAfter ) || ( file.bad() ) )
        return false;
    if( bf_fNme != ( char ) file.get() )
        return false;
    std::string funcName = parseName( file, bf_fNme );

    if( 0 == funcName.size() )
        return false;

    proc addProc;
    addProc.realCodeSize = 0;

    while( file.good() )
    {
        char tmp = (char) file.get();
        
        if( bf_cBeg == tmp )
            skipComment( file );
        else if ( bf_cEnd == tmp )
            ; // Syntax error
        else if ( bf_fEnd == tmp )
        {
            std::map<std::string, proc>::iterator it = procedures.find( funcName );
            if( it == procedures.end() )
            {
                std::pair< std::string, proc > tmpP;
                tmpP.first = funcName;
                tmpP.second = addProc;
                procedures.insert( tmpP );
                std::cout << "Added proceudre: " << funcName << std::endl;
                printCode( addProc.code, addProc.realCodeSize, funcName );
                return true;
            }
            else
            {
                std::cerr << "Function with name " << funcName
                     << " already exists. Ignoring all other definitions."
                     << std::endl;
                return false;
            }
        }
        else
            addProc.code[ addProc.realCodeSize++ ] = tmp;

    }
    return false;
}


Итак, у нас есть множество функций, комментарии, и функция loop для выполнения всего этого.
Добавим еще 2 символа в наш язык:

  • # — увеличить значение регистра отвечающего за текущий while, если таковой имеется.
  • ~ — уменьшить начение регистра текущего while-a.


Вот собственно и все. Теперь, когда в коде встречается символ '@', найдем имя функции, и по имени найдем тело функции, которая должна выполнится. Скопируем регистры для нее, и вызовем функцию loop.

Исходный код.
Tags:
Hubs:
+2
Comments 8
Comments Comments 8

Articles