diff --git a/recursiveDescentParsers/cplusplus/interpret++/.gitignore b/recursiveDescentParsers/cplusplus/interpret++/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c8058545a33d4bdc3c2be1d1db929b22f571edea --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/.gitignore @@ -0,0 +1,4 @@ +interpret++ +*.o +.deps/ + diff --git a/recursiveDescentParsers/cplusplus/interpret++/Makefile b/recursiveDescentParsers/cplusplus/interpret++/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3471232ec42ff88aeacc43b54bbae9f9fe5d8f33 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/Makefile @@ -0,0 +1,78 @@ +# This Makefile made available to his students by +# Prof. Ronald Moore +# https://fbi.h-da.de/personen/ronald-moore/ +# mailto:ronald.moore@h-da.de +# with no warranties whatsoever + + +PROGS := interpret++ +SOURCES := main.cpp lexer.cpp parser.cpp +OBJS = $(SOURCES:.cpp=.o) + +# Uncomment only one of the next two lines (choose your c++ compiler) +# CC=g++ +CC := clang++ + +## Add your own CFLAGS if you find them necessary... such as -O3 or so... +## -g for debugging +## -std=<whatever> to select the right C++ Version +## -fmessage-length=0 disallows line wrapping in error messages +## (helps some IDEs (still?)) +CPPFLAGS := -g -std=c++17 -Wall -fmessage-length=0 + + +## More preliminaries +# See https://www.gnu.org/software/make/manual/html_node/Special-Targets.html +# In this makefile, we want to keep going even if we find errors +.IGNORE : + +# Tell make that the following "targets" are "phony" +# Cf. https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html#Phony-Targets +.PHONY : all clean tests + +# This absolutely needs to be the first target (so to be the default target) +all: $(PROGS) + +# Some of the "Automatic Variables" that can be used in Makefiles. +# Cf. https://www.gnu.org/software/make/manual/ - particularly +# https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html#Automatic-Variables +# $@ = The filename representing the target. +# $< = The filename of the first prerequisite. +# $(*F) = The stem of the filename of the target (i.e. without .o, .cpp...) +# $^ = The names of all the prerequisites, with spaces between them. + +## Following magic is used to figure out which dot cpp files depend +# on which headers (dot h files) -- automatically (so that we recompile +# only the ncessary dot cpp files when a header is maodified). +# Magic taken from +# http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/ +# ... and then fixed, and fixed, and fixed some more. +DEPDIR := .deps +# Broken: DEPFLAGS = -MT $@ -MMD -MP -MF $(DEPDIR)/$*.d +DEPFLAGS = -MMD -MF $(DEPDIR)/$*.d + +# include dep files, if they exist (minus says don't complain if they don't) +DEPS := $(OBJS:%.o=$(DEPDIR)/%.d) + +-include $(DEPS) + +%.o : %.cpp %.d + $(CC) -c $(CPPFLAGS) $(DEPFLAGS) -o $@ $< + +# Make depdir if it doesn't exist... +$(DEPDIR): ; @mkdir -p $@ + +## Now, the targets -- the things that will get made! + +$(PROGS): $(OBJS) + $(CC) $(CPPFLAGS) $(OBJS) $(LIBS) -o $@ + +clean: + $(RM) -v *~ *.o $(PROGS) tmp.txt + $(RM) -fvr $(DEPDIR) + # starting recursive clean... + cd tests && $(MAKE) clean + +tests: $(PROGS) + # Going to the tests directory for testing + cd tests && $(MAKE) tests diff --git a/recursiveDescentParsers/cplusplus/interpret++/README.md b/recursiveDescentParsers/cplusplus/interpret++/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1f1c217fd727cad8a8f1da68bce0979ef52dd508 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/README.md @@ -0,0 +1,68 @@ +Overview +======== + +Everything here is taken from the slides for the "Compiler Construction" +course, i.e. it is directly from Prof. Ronald C. Moore. + +Or at least, after all this time, I really don't remember having stolen +this code from somewhere else, but if you find an older copy that looks +similar, please let me know, so I can give credit where credit is due -- +or disavow knowledge of that source (as the case may be). + +You are in the following subdirectory + ** `interpret++` + This code take mathematical expressions and evaluates them, + i.e. it outputs numbers. It is the same as its sister folder `interpreter` + as far as recursive descent parsing goes, but uses more C++ features and + is set up so as to support growing up to be a larger project. + +See **Chapter 1 Front End Construction**, Slides 21 and 22 (and please let me know when the inevitable day comes that these slide numbers are no longer correct). + +Building and Running +==================== + +This version requires a C++17 compatible C++ compiler, +such as newer versions of `g++` or `clang`. + +Build the program by running `make`. + +In case of doubt, use the voodo command `make clean` and then repeat `make`. + +To test the program, run `make tests`. This also illustrates how the intepreter is used. + +Alternatively, just run the program with *no* parameters (i.e. simply `./intepret++`). +It will tell you how it wants to be run (Hint: there are two usages). + +Contents (Manifest) +==================== + +You should find here: + +* `lexer.cpp` and `lexer.h` + Source code for the lexer. + +* `parser.cpp` and `parser.h` + Source code for the recursive descent parser. + +* `main.cpp` + Source code for `main` -- the driver. + +* `Makefile` + Used to run `make` (obviously?). + +* `tests` + A directory full of test cases. Run `make tests` + (either in directory `tests` or the parent directory) + to run the tests. Not unit tests, rather acceptance tests, + but regression tests all the same. + +* `README.md` + This file. + +Ronald Moore +https://fbi.h-da.de/personen/ronald-moore/ +ronald.moore@h-da.de + + +1 May 2020 + diff --git a/recursiveDescentParsers/cplusplus/interpret++/interpreter.cpp b/recursiveDescentParsers/cplusplus/interpret++/interpreter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c481e21910b2a1b85ad5d2b14a10fe9f729df087 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/interpreter.cpp @@ -0,0 +1,244 @@ +// 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 +// 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: +// 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.... +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; + +static Token next_token; // again with the global variables... +static numberType currentNumber; // = zero.... + +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 + +// The Lexer +// ========== +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. + }; +}; + +static Token gettok( ) { + assert( input ); // we assume nullptr != input + if ( ! *input ) return bad_tok; + // else, we can read from input + + // 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(); + + 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; + }; +} + + +// main (!) +// ========= + +int main( int argc, char **argv ) { + + 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; + }; + std::cout << "End Of File!" << std::endl; + + return 0; // Alles klar!!! +} diff --git a/recursiveDescentParsers/cplusplus/interpret++/lexer.cpp b/recursiveDescentParsers/cplusplus/interpret++/lexer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f09c5715285c145cd768084b4165b3738f34cf61 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/lexer.cpp @@ -0,0 +1,141 @@ +// 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 +// with no warranties whatsoever! + +#include "lexer.h" + +#include <cassert> +#include <cctype> // for isspace +#include <cstdlib> // for strtod +#include <iostream> +#include <fstream> + +namespace lex { // continue to define things in lex:: + +// instantiate here... +lex::Token next_token; // again with the global variables... + +// global variables -- sue me if you don't like that! +static std::string inputSourceName( "standard input" ); +static std::istream *input = &(std::cin); // until proven otherwise + +static std::string currentLine( "" ); +static int currentLineNumber = -1; +static int currentColumnNumber = 0; + +// Namespace "member functions" +// ============================ + +void printInputLocation( ) { + std::cout << inputSourceName + << " (" << currentLineNumber + << ',' << currentColumnNumber + << "):" << std::endl; + std::cout << currentLine << std::endl; + for ( int col = 0; col < currentColumnNumber; col++ ) + std::cout << '-'; + std::cout << '^' << std::endl; +} // end printInputLocation + +void printErrorMsg( const std::string Error ) +{ + printInputLocation( ); + std::cout << "ERROR : " << Error << std::endl; + advance_token( ); // Don't want to get stuck here. +} // end printErrorMsg + +// Utility skippedWhiteSpace... +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. + }; +}; + +Token gettok( ) { + assert( input ); // we assume nullptr != input + + Token result( bad_tok, '\0' ); // default + + if ( ! *input ) return result; // i.e. bad_tok + // else, we can read from input + + // Skip white space, going to next line as necessary + if ( ! skippedWhiteSpace( ) ) { + result.first = lex::tok_eof; + return result; + }; + + // else -- not eof, d.h. we have visible text in front of us. + char currentChar = currentLine[ currentColumnNumber ]; + result.second = currentChar; // unless it's a number, etc. + currentColumnNumber++; // usually, but see num... + switch ( currentChar ) { + case '(' : result.first = tok_lparen; + break; + case ')' : result.first = tok_rparen; + break; + case '+' : result.first = tok_plus; + break; + case '-' : result.first = tok_minus; + break; + case '*' : result.first = tok_times; + break; + case '/' : result.first = tok_div; + break; + default : + // either we have a number in front of us, or we don't + assert( 0 < currentColumnNumber ); // remember, incremented! + 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 ); + // strtod sets omega to the first char after the number + if ( alpha == omega ) { + result.second = *omega; // or *alpha, they're the same... + return result; // i.e. bad_tok !!! + }; + // else if strtod found a real number (or at least a double) + result.first = tok_number; + result.second = tmpValue; // let C++ do any converisons + currentColumnNumber += (omega - alpha) -1; + // minus one because we incremented it before the switch + + }; // end switch + return result; +} // end gettok + +void openInputSource( std::string filename ) { + assert( ! filename.empty() ); // caller should check that + inputSourceName = filename; + if ( "-" == filename ) + input = &(std::cin); + else { // if fileName is not "-" (a dash) + static std::ifstream ifs( filename, std::ifstream::in ); + if ( ! ifs.good( ) ) { + std::cerr << "ERROR opening file name " << filename + << " -- could not open.\n"; + exit( -2 ); + }; + // else if ifs is good + input = &ifs; + }; +} // end of openInputFile + +} // end namespace lex diff --git a/recursiveDescentParsers/cplusplus/interpret++/lexer.d b/recursiveDescentParsers/cplusplus/interpret++/lexer.d new file mode 100644 index 0000000000000000000000000000000000000000..0ad102ac4d6147955d9722e444693a2e72479e2a --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/lexer.d @@ -0,0 +1 @@ +lexer.o: lexer.cpp lexer.h diff --git a/recursiveDescentParsers/cplusplus/interpret++/lexer.h b/recursiveDescentParsers/cplusplus/interpret++/lexer.h new file mode 100644 index 0000000000000000000000000000000000000000..cd3182302c0d6e64e5b891a4f6407b3c4b50bbab --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/lexer.h @@ -0,0 +1,65 @@ +// 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 +// with no warranties whatsoever! + +#pragma once + +#include <cctype> // for isspace +#include <cstdlib> // for strtod +#include <string> +#include <utility> // for std::pair +#include <variant> // new C++17 feature! Like unions, only better! + +// =================== +// LEXICAL ANALYSIS +// The following are taken to be tokens: +// 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 +// ============================ + +namespace lex { + +// Utility Types +typedef double numberType; // feel fee to change this to something else like int or float or bigint.... + +// Tokens -- are a pair of a tag and a value, where the value can be +// various things - a char or a numberType at present, but names and +// multicharacter operators could be added later +typedef enum { + tok_number = 'n', + tok_lparen = '(', + tok_rparen = ')', + tok_plus = '+', + tok_minus = '-', + tok_times = '*', + tok_div = '/', + tok_eof = 'E', + bad_tok = 'X' +} TokenTag; + +typedef std::variant< char, numberType > TokenValue; + +typedef std::pair< TokenTag, TokenValue > Token; + +extern Token next_token; // again with the global variables... { + +// Functons (or methods, if you prefer) + +void printInputLocation( ); + +void printErrorMsg( const std::string Error ); + +Token gettok( ); // + +// DRY -- this line is repeated so often, it deserves its own function +inline void advance_token( ) { next_token = gettok(); } // eats current token! + +void openInputSource( std::string filename ); + +} // end namespace lex diff --git a/recursiveDescentParsers/cplusplus/interpret++/main.cpp b/recursiveDescentParsers/cplusplus/interpret++/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfa4f0d620945def8bd8b25d28651ba837870522 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/main.cpp @@ -0,0 +1,42 @@ +// 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 +// with no warranties whatsoever! + +#include <iostream> + +#include "parser.h" // this chain-includes lexer.h + +// main (!) +// ========= + +int main( int argc, char **argv ) { + + if (2 != argc) { + std::cerr << "Usage: " << argv[0] << " <fileName> \n" + << " " << argv[0] << "- (to read from standard input) \n" + << "You provided " << argc-1 << " arguments, we take exactly one (only).\n"; + return( -1 ); + }; + // else if 1 == argc .... + lex::openInputSource( argv[1] ); + + // Prime the pump! + lex::advance_token( ); + + // get tokens and dump them... + while ( lex::tok_eof != lex::next_token.first ) { + lex::printInputLocation( ); + lex::numberType value = parse::E( ); + std::cout << "INTERPRETER: " << value << std::endl; + if ( lex::bad_tok == lex::next_token.first ) { + std::string errorMsg( "Unrecognized character(s)=" ); + errorMsg.push_back( std::get< char >( lex::next_token.second ) ); + lex::printErrorMsg( errorMsg ); + }; // end if bad token + }; // end while not eof + std::cout << "End Of File!" << std::endl; + + return 0; // Alles klar!!! +} diff --git a/recursiveDescentParsers/cplusplus/interpret++/main.d b/recursiveDescentParsers/cplusplus/interpret++/main.d new file mode 100644 index 0000000000000000000000000000000000000000..e1e4c9197aa3f2ff876de194daed1c9fd1bbc71a --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/main.d @@ -0,0 +1 @@ +main.o: main.cpp parser.h lexer.h diff --git a/recursiveDescentParsers/cplusplus/interpret++/parser.cpp b/recursiveDescentParsers/cplusplus/interpret++/parser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d699dc0049379bf68cca75ae4e95d1e3025b2dac --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/parser.cpp @@ -0,0 +1,100 @@ +// 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 +// with no warranties whatsoever! + + +#include <cassert> + +#include "parser.h" + +namespace parse { + + +// 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"- + +// For every non-terminal (E, E2ndHalf, T, etc.) there is one function: + +// E → T E´ +lex::numberType E() { return T() + E2ndHalf(); } + +// T → F T´ +lex::numberType T() { return F() * T2ndHalf(); } + +// E´ → + T E´ | - T E´ | ε +lex::numberType E2ndHalf() { + switch ( lex::next_token.first ) { + case lex::tok_plus : + lex::advance_token( ); // eat + + return T() + E2ndHalf(); + + case lex::tok_minus : + lex::next_token = lex::gettok(); // eat - + return (-1.0 * T()) + E2ndHalf(); + + default : + return 0.0; + }; +} // end E2ndHalf + +// T´ → * F T´ | / F T´ | ε +lex::numberType T2ndHalf() { + lex::numberType tmp; + switch ( lex::next_token.first ) { + case lex::tok_times : + lex::advance_token( ); // eat * + return F() * T2ndHalf(); + case lex::tok_div : + lex::advance_token( ); // eat / + tmp = F(); + if ( 0.0 != tmp ) + return (1.0/tmp) * T2ndHalf(); + // else if T() returned zero + lex::printErrorMsg( "Division by zero!" ); + // fall through to default return one + + default : + return 1.0; + }; +} // end T2ndHalf + +// F → ( E ) | num +lex::numberType F() { + lex::numberType result = 0; + switch ( lex::next_token.first ) { + case lex::tok_lparen : + lex::advance_token( ); // eat lparen + result = E(); + if ( lex::tok_rparen == lex::next_token.first ) { + lex::advance_token( ); // eat rparen + return result; + }; + // else if rparen not found + lex::printErrorMsg( "Expected Right Parenthesis" ); + return 0.0; + + case lex::tok_number : + result = std::get< lex::numberType >( lex::next_token.second ); + lex::advance_token( ); // eat id + return result; + + default : + lex::printErrorMsg( "Expected Left Parenthesis or number" ); + return 0.0; + }; +} + + +} // end namespace parse diff --git a/recursiveDescentParsers/cplusplus/interpret++/parser.d b/recursiveDescentParsers/cplusplus/interpret++/parser.d new file mode 100644 index 0000000000000000000000000000000000000000..2fb708bf31118a18c5449e73e29954b42def6e33 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/parser.d @@ -0,0 +1 @@ +parser.o: parser.cpp parser.h lexer.h diff --git a/recursiveDescentParsers/cplusplus/interpret++/parser.h b/recursiveDescentParsers/cplusplus/interpret++/parser.h new file mode 100644 index 0000000000000000000000000000000000000000..2fb9f955ff6f8d55a8d4388a046f0ad15b9764bd --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/parser.h @@ -0,0 +1,33 @@ +// 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 +// with no warranties whatsoever! + +#pragma once + +#include "lexer.h" + +namespace parse { + +// 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 +lex::numberType E(); +lex::numberType E2ndHalf(); +lex::numberType T(); +lex::numberType T2ndHalf(); +lex::numberType F(); + +} // end namespace parse diff --git a/recursiveDescentParsers/cplusplus/interpret++/tests/Makefile b/recursiveDescentParsers/cplusplus/interpret++/tests/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..5bbf7245755453667f425ef63e833da99d1dcced --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/tests/Makefile @@ -0,0 +1,106 @@ +# This Makefile made available to his students by +# Prof. Ronald Moore +# https://fbi.h-da.de/personen/ronald-moore/ +# mailto:ronald.moore@h-da.de +# with no warranties whatsoever + + +## More preliminaries +# See https://www.gnu.org/software/make/manual/html_node/Special-Targets.html +# In this makefile, we want to keep going even if we find errors +.IGNORE : + +.NOTPARALLEL : + +## Define test collections - each line should contain one or more filenames +GOOD-INPUTS := goodTest.input +BAD-INPUTS := badTest.input + +# the sum of all tests +INPUTS := $(GOOD-INPUTS) $(BAD-INPUTS) + +# reference files ("correct" output) +REFERENCES := $(INPUTS:.input=.reference) + +# output files +OUTPUTS := $(INPUTS:.input=.output) + +# Program we'll be testing +PROGRAM := ../interpret++ + +# Tell make that the following "targets" are "phony" +# Cf. https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html#Phony-Targets +.PHONY : all clean tests goodtests badtests + +## Now, the targets -- the things that will get made! + +all: tests + +# For convenience, "make check" == "make test" +check: tests + +# TESTING +# Calling "make test" should +# (1) make the programs (if necessary) +# (2) erase all the test outputs +# (3) create them again (see rules, below). +# (4) run the special case "bigtest" (see below) +tests: $(PROGRAM) + cd .. && $(MAKE) + $(MAKE) clean + $(MAKE) updatetests + +updatetests: $(OUTPUTS) + +$(PROGRAM) : + cd .. + $(MAKE) + +# "make clean" or equivalently "make testclean" deletes all files created by testing +clean: testclean + +testclean: + $(RM) -fv *.output *~ + +# By the way, for more information about calling make from make, see +# https://www.gnu.org/software/make/manual/html_node/Recursion.html#Recursion + +# User-defined function (Cf. http://oreilly.com/catalog/make3/book/ch04.pdf) +# Takes two arguments - an output file and a reference output file +# (the two files should be the same). Function prints "success" or "failure", +# depending on whether the two files are equal. +#define testReferenceOutput +# @cmp -s $1 $2 \ +# && /bin/echo -e "Test against $2 successful" \ +# || /bin/echo -e "\n\tTest against $2 FAILED!!!\n" +#endef + +#### If your terminal supports colors, comment out the version above, +# and use this version instead - good tests have a green background, +# failed tests have a red background. See +# <http://misc.flogisoft.com/bash/tip_colors_and_formatting> +# for information about output formatting (colors, bold, etc.)). + +define testReferenceOutput +# @cmp -s $1 $2 + @diff -qs $1 $2 \ + && /bin/echo -e "\e[1;42mTest against $2 successful.\e[0m" \ + || /bin/echo -e "\e[1;41mTest against $2 FAILED!!! \e[0m" +endef + +# Reminder... +# $@ = The filename representing the target. +# $< = The filename of the first prerequisite. +# $* = The stem of the target (i.e. without .o, .cpp...) +# $(*F) = The stem of the target (i.e. without .o, .cpp... AND without directory) +# $^ = The names of all the prerequisites, with spaces between them. + + +################ Assembler Tests ################### +# For some files x.job, we have stored the "correct" (expected) output in x.ref +# (where "ref" is short for "reference"). +$(OUTPUTS): %.output: %.input %.reference + ../interpret++ $< >$@ 2>&1 + $(call testReferenceOutput,$@, $*.reference) + +# Finished! diff --git a/recursiveDescentParsers/cplusplus/interpret++/tests/badTest.input b/recursiveDescentParsers/cplusplus/interpret++/tests/badTest.input new file mode 100644 index 0000000000000000000000000000000000000000..99faffe929d271a664703888821ac01a3bf91828 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/tests/badTest.input @@ -0,0 +1,8 @@ +42? +42^2 +(42)) +((42 +(42 / 0) +(42 / (1-1)) +42 +/- 3.14159 + diff --git a/recursiveDescentParsers/cplusplus/interpret++/tests/badTest.reference b/recursiveDescentParsers/cplusplus/interpret++/tests/badTest.reference new file mode 100644 index 0000000000000000000000000000000000000000..7e3895c10067b5714af2b4eadee85568d6f24768 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/tests/badTest.reference @@ -0,0 +1,65 @@ +badTest.input (0,2): +42? +--^ +INTERPRETER: 42 +badTest.input (0,3): +42? +---^ +ERROR : Unrecognized character(s)=? +badTest.input (1,2): +42^2 +--^ +INTERPRETER: 42 +badTest.input (1,3): +42^2 +---^ +ERROR : Unrecognized character(s)=^ +badTest.input (1,4): +42^2 +----^ +INTERPRETER: 2 +badTest.input (2,1): +(42)) +-^ +INTERPRETER: 42 +badTest.input (2,5): +(42)) +-----^ +badTest.input (2,5): +(42)) +-----^ +ERROR : Expected Left Parenthesis or number +INTERPRETER: 0 +badTest.input (3,1): +((42 +-^ +badTest.input (4,1): +(42 / 0) +-^ +ERROR : Expected Right Parenthesis +badTest.input (4,3): +(42 / 0) +---^ +ERROR : Expected Right Parenthesis +badTest.input (4,8): +(42 / 0) +--------^ +ERROR : Division by zero! +INTERPRETER: 0 +badTest.input (5,1): +(42 / (1-1)) +-^ +badTest.input (5,12): +(42 / (1-1)) +------------^ +ERROR : Division by zero! +badTest.input (6,2): +42 +/- 3.14159 +--^ +ERROR : Expected Right Parenthesis +badTest.input (6,5): +42 +/- 3.14159 +-----^ +ERROR : Expected Left Parenthesis or number +INTERPRETER: -3.14159 +End Of File! diff --git a/recursiveDescentParsers/cplusplus/interpret++/tests/goodTest.input b/recursiveDescentParsers/cplusplus/interpret++/tests/goodTest.input new file mode 100644 index 0000000000000000000000000000000000000000..7c49594be4b299e555ffbe7865109c33dcbf9497 --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/tests/goodTest.input @@ -0,0 +1,11 @@ +42 +40 + 2 +(44.44 - 2.44) +(21 * 2.0) +(84.0 / (3-1)) +88 / 2 - 2 +88 / (8 / 4) - 2 +44.0 - 2 +(4 * 11.0) - 2 +(8 * 11.0) / 2 - 2 +(8 * 11.0) / (8 / 4) - 2 diff --git a/recursiveDescentParsers/cplusplus/interpret++/tests/goodTest.reference b/recursiveDescentParsers/cplusplus/interpret++/tests/goodTest.reference new file mode 100644 index 0000000000000000000000000000000000000000..828a2999e3f5b7f8448a9b2f1de890a83ff3a25d --- /dev/null +++ b/recursiveDescentParsers/cplusplus/interpret++/tests/goodTest.reference @@ -0,0 +1,45 @@ +goodTest.input (0,2): +42 +--^ +INTERPRETER: 42 +goodTest.input (1,2): +40 + 2 +--^ +INTERPRETER: 42 +goodTest.input (2,1): +(44.44 - 2.44) +-^ +INTERPRETER: 42 +goodTest.input (3,1): +(21 * 2.0) +-^ +INTERPRETER: 42 +goodTest.input (4,1): +(84.0 / (3-1)) +-^ +INTERPRETER: 42 +goodTest.input (5,2): +88 / 2 - 2 +--^ +INTERPRETER: 42 +goodTest.input (6,2): +88 / (8 / 4) - 2 +--^ +INTERPRETER: 42 +goodTest.input (7,4): +44.0 - 2 +----^ +INTERPRETER: 42 +goodTest.input (8,1): +(4 * 11.0) - 2 +-^ +INTERPRETER: 42 +goodTest.input (9,1): +(8 * 11.0) / 2 - 2 +-^ +INTERPRETER: 42 +goodTest.input (10,1): +(8 * 11.0) / (8 / 4) - 2 +-^ +INTERPRETER: 42 +End Of File!