Skip to content
Snippets Groups Projects
interpreter.cpp 6.49 KiB
Newer Older
  • Learn to ignore specific revisions
  • Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    // This code made available to his students by 
    // Prof. Ronald Moore  
    //     https://fbi.h-da.de/personen/ronald-moore/  
    //     mailto:ronald.moore@h-da.de
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    // with no warranties whatsoever!
    
    
    #include <cassert>
    #include <cctype> // for isspace
    #include <cstdlib> // for strtod
    #include <iostream>
    #include <fstream>
    #include <string>
    // include <vector>
    
    // ===================
    // LEXICAL ANALYSIS
    // The following are taken to be tokens:
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    // left and right parenthesis, the plus and minus characters,
    // as well as asterisk and forward slash -- and numbers.
    // In the script, substraction and division are not supported,
    // but it seems like time to add them.
    
    // Preliminaries and Utilities
    // ============================
    
    // Utility Types
    typedef double numberType; // feel fee to change this to something else like int or float or bigint....
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    typedef enum Token { 
    	tok_number = 'n',
    	tok_lparen = '(',
    	tok_rparen = ')',
    	tok_plus   = '+',
    	tok_minus  = '-',
    	tok_times  = '*',
    	tok_div    = '/',
    	tok_eof    = 'E',
    	bad_tok    = 'X'
    } Token;
    
    // global variables -- sue me if you don't like that!
    static std::istream *input = &(std::cin); // until proven otherwise
    static std::string	currentLine( "" );
    static int	currentLineNumber = -1;
    static int	currentColumnNumber = 0;
    static int	currentTokenLength = 0;
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    static Token next_token; // again with the global variables... 
    static numberType currentNumber; // = zero....
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    
    
    static void printErrorMsg( const std::string Error )
    {
    	std::cout << "ERROR on line " << currentLineNumber
    	          << ", column " << currentColumnNumber << " : " 
    	          << Error << std::endl;
    	std::cout << currentLine;
    	for ( int col = 0; col < currentColumnNumber-1; col++ )
    		std::cout << '-';
    	std::cout << '^' << std::endl;
    } // end printErrorMsg
    
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    // The Lexer
    // ==========
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    static bool skippedWhiteSpace( ) { // return true if not at EOF, i.e. if skipped
    	while ( true ) {
    		int currentLineLength = currentLine.length();
    		while ( currentColumnNumber < currentLineLength )
    			if ( isspace( currentLine[ currentColumnNumber ] ) ) 
    				currentColumnNumber++;
    			else // if NOT isspace() 
    				return true;
    				
    		// if we're here, we're at the end of a line.
    		std::getline( *input, currentLine ); 
    		currentLineNumber++;
    		currentColumnNumber = 0;
    		if ( ! *input ) // EOF!!
    			return false;
    		// else, repeat! 
    		// Which is the same as 
    		// return skippedWhiteSpace()  -- i.e. tail recursion.
    	};
    };		
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    static Token gettok( ) {
    	assert( input ); // we assume nullptr != input
    	if ( ! *input ) return bad_tok;
    	// else, we can read from input
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    	
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    	// Skip white space, going to next line as necessary
    	if ( ! skippedWhiteSpace( ) ) return tok_eof;
    	        
    	// We're have visible text in front of us.
    	char currentChar = currentLine[ currentColumnNumber ];
    	currentColumnNumber++; // usually, but see num...
    	switch (  currentChar ) {
    		case '(' : return tok_lparen;
    		case ')' : return tok_rparen;
    		case '+' : return tok_plus;
    		case '-' : return tok_minus;
    		case '*' : return tok_times;
    		case '/' : return tok_div;
    		default : 
    			// either we have a number in front of us, or we don't
    			assert( 0 < currentColumnNumber );
    			char *alpha = &(currentLine[ currentColumnNumber-1 ]);
    			// minus one because we incremented it before the switch
    			char *omega = nullptr; // until we call strtod...
    			double tmpValue = strtod( alpha, &omega );
    			if ( alpha == omega ) {
    				return bad_tok; // !!!
    			};
    			// else if strtod found a real number (or at least a double)
    			currentNumber = tmpValue; // let C++ do the converison
    			currentColumnNumber += (omega - alpha) -1;
    			// minus one because we incremented it before the switch
    			return tok_number;
    	
    	}; // end switch
    	assert( false ); // we should never get here!
    	return bad_tok;
    } // end gettok
    	
    // PARSING!!!
    // ===========
    // 
    // The grammar we are going to parse here is:
    // Grammar:
    // 	E	→ T E´
    // 	E´	→ + T E´  | - T E´ | ε
    // 	T	→ F T´
    // 	T´	→ * F T´  |  / F T´  |  ε
    // 	F	→ ( E ) | num
    // Note that the recursive descent function for (e.g.) E´ 
    // is nameded "E2ndHalf"-
    
    
    // Forward Declarations
    static numberType E();
    static numberType E2ndHalf();
    static numberType T();
    static numberType T2ndHalf();
    static numberType F();
    
    // 	E	→ T E´
    numberType E() { return T() + E2ndHalf(); }
    
    // 	T	→ F T´
    numberType T() { return F() * T2ndHalf(); }
    
    // 	E´	→ + T E´  | - T E´ | ε
    numberType E2ndHalf() {
        switch ( next_token ) {
    		case tok_plus : 
    			next_token = gettok(); // eat +
                return T() + E2ndHalf();
    
    		case tok_minus : 
    			next_token = gettok(); // eat -
    			return (-1.0 * T()) + E2ndHalf();
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    
    
    		default : 
    			return 0.0;
        }; 
    } // end E2ndHalf
    
    // 	T´	→ * F T´  |  / F T´  |  ε
    numberType  T2ndHalf() { 
    	numberType tmp, rhs, acc;
        switch ( next_token ) {
    		case tok_times : 
    			next_token = gettok(); // eat *
    			return F() * T2ndHalf();
        case tok_div : 
    		next_token = gettok(); // eat /
    		tmp = F();
    		if ( 0.0 != tmp ) 
    			return (1.0/tmp) * T2ndHalf();
    		// else if T() returned zero
    		printErrorMsg( "Division by zero!" );
    		// fall through to default return one
            
        default : 
    		return 1.0;
        }; 
    } // end T2ndHalf
    
    // 	F	→ ( E ) | num
    numberType  F() {
    	numberType result = 0; 
        switch ( next_token ) {
    		case tok_lparen : 
    			next_token = gettok(); // eat lparen
                result = E();
                if ( tok_rparen == next_token ) {
    				next_token = gettok(); // eat rparen
                    return result;
    			}; 
    			// else if rparen not found
                printErrorMsg( "Expected Right Parenthesis" );
                return 0.0;
    
    		case tok_number : 
    			result = currentNumber; // side-effect of last gettok() 
                next_token = gettok(); // eat id
                return result;
    
    		default : 
    			printErrorMsg( "Expected Left Parenthesis or number" );
                return 0.0;
        }; 
    }
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    // main (!)
    // =========
    
    int main( int argc, char **argv ) {
    
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    	if (2 != argc) {
    		std::cerr << "Usage: " << argv[0] << " <fileName>.\n" 
    		          << "You provided " << argc-1 << " arguments, we take exactly one (only).\n";
    		return( -1 );
    	};
    	// else if 1 == argc ....
    	std::string	fileName( argv[1] );
    	if ( "-" != fileName ) {
    		static std::ifstream ifs( fileName );
    		input = &ifs;
    	}
    
    	// Prime the pump!
    	next_token = gettok( );
    	
    	// get tokens and dump them... 
    	while ( tok_eof != next_token ) {
    
    		std::cout << currentLineNumber << ":" 
    		          << currentLine << std::endl;
    		numberType value = E( );
    		std::cout << "INTERPRETER: " << value << std::endl;
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    	};
    
    	std::cout << "End Of File!" << std::endl;
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    	
    
    Ronald Charles Moore's avatar
    Ronald Charles Moore committed
    	return 0; // Alles klar!!!
    }