📜 ⬆️ ⬇️

We write an interpreter for your esoteric language (part 2)

Let me remind you that in the previous article I set the task to write an interpreter for the add-on Brainfuck. Naturally, for the beginning it was necessary to implement Brainfuck itself, and only then move on to the add-in. Fortunately, in the previous article this part was implemented. Actually we will describe what should be implemented in this part:
  1. The concept of function (procedure).
  2. Comments


Comments

This is the easiest part. We will consider as a comment any sequence of characters enclosed in parentheses. Note that the bracket sequence "framing" the text of the comment must be correct. It is obvious. That's all. And one more thing: comments should be “eliminated” at the beginning of the parsing - at the end they should receive only the code that needs to be executed. This is done simply:

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;
}


The function will be called as soon as the symbol '(' is encountered. It “swallows” the comment and returns the number of characters swallowed. That's all, the question is solved. Let us proceed to the next task.

Functions / Procedures.

Here, of course, it will be more difficult!
Require:

So the function body should have the form:
{% <name>% <code>}
')
The name can be any sequence of characters without an opening parenthesis.

To search for the function name, use the function:

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;
}


This function will be useful to us when we need to determine the name of the function in order to call it. To store the function we will use the structure:

struct proc
{
char code[ maxCodeSize ]; //
unsigned int realCodeSize; //
};

And we will store functions in the std :: map <std :: string, proc> procedures, as the key we will use the file and fill in the function identifier. It remains to learn to parse the file and fill the container procedures. Here is:

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;
}


So we have a lot of functions, comments, and a loop function to do all this.
Add 2 more characters to our language:



That's all. Now, when the '@' symbol is found in the code, we will find the name of the function, and by the name we will find the body of the function that should be executed. Copy the registers for it, and call the loop function.

Source.

Source: https://habr.com/ru/post/120279/


All Articles