Напомню, что в предыдущей статье я поставил задачу написать интерпретатор для надстройки над Brainfuck. Естественно, что для начала нужно было реализовать сам Brainfuck, и только затем переходить к надстройке. Благо в предыдущей статье эта часть была реализована. Собственно опишем то, что следует реализовать в этой части:
Это самая легкая часть. Комментарием будем считать всякую последовательность символов заключенных в круглые скобки. Заметим, что скобочная последовательность «обрамляющая» текст комментария обязательно должна быть правильной. Это очевидно. Вот собственно и все. И еще: комментарии должны «отсеиваться» в начале парсинга — в конце должны получить только код, который надо выполнить. Это делается просто:
Функция вызовется сразу как встретится символ '('. Она «проглотит» комментарий и вернет количество проглоченных символов. Все, вопрос решен. Перейдем к следующей задаче.
Тут, конечно, посложнее будет!
Потребуем:
Итак тело функции должно иметь вид:
{%<имя>%<код>}
Именем может служить любая последовательность символов без открывающей круглой скобки.
Для поиска имени функции используем функцию:
Эта функция нам будет полезна, когда нужно будет определить имя функции, чтоб ее вызвать. Для хранения функции будем использовать структуру:
И будем хранить функции в std::map< std::string, proc > procedures, как ключ будем использовать файл и наполнять идентификатор функции. Осталось научиться парсить файл и наполнять контейнер procedures. Вот:
Итак, у нас есть множество функций, комментарии, и функция loop для выполнения всего этого.
Добавим еще 2 символа в наш язык:
Вот собственно и все. Теперь, когда в коде встречается символ '@', найдем имя функции, и по имени найдем тело функции, которая должна выполнится. Скопируем регистры для нее, и вызовем функцию loop.
Исходный код.
- Понятие функции(процедуры).
- Комментарии.
Комментарии
Это самая легкая часть. Комментарием будем считать всякую последовательность символов заключенных в круглые скобки. Заметим, что скобочная последовательность «обрамляющая» текст комментария обязательно должна быть правильной. Это очевидно. Вот собственно и все. И еще: комментарии должны «отсеиваться» в начале парсинга — в конце должны получить только код, который надо выполнить. Это делается просто:
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.
Исходный код.