/* * Copyright (C) 2007-2023 Xagasoft, All rights reserved. * * This file is part of the libbu++ library and is released under the * terms of the license contained in the file LICENSE. */ #include #include #include #include #include #include using namespace Bu; class Test { public: Test() : bExpectPass( true ) { } Bu::String sName; bool bExpectPass; }; typedef Bu::List TestList; class Suite { public: Bu::String sName; TestList lTest; }; //typedef Bu::List SuiteList; enum TokType { tokFluff, tokSuite, tokTest, tokChar, tokBlock, tokCleanup, tokEof }; Bu::Formatter &operator<<( Bu::Formatter &f, TokType t ) { switch( t ) { case tokFluff: return f << "tokFluff"; case tokSuite: return f << "tokSuite"; case tokTest: return f << "tokTest"; case tokChar: return f << "tokChar"; case tokBlock: return f << "tokBlock"; case tokCleanup: return f << "tokCleanup"; case tokEof: return f << "tokEof"; } return f; } Bu::Formatter &operator<<( Bu::Formatter &f, const Test &t ) { return f << "{" << t.sName << ", bExpectPass=" << t.bExpectPass << "}"; } Bu::Formatter &operator<<( Bu::Formatter &f, const Suite &s ) { return f << "Suite[" << s.sName << "] = " << s.lTest << f.nl; } class Parser { public: Parser( const Bu::String &sFile ) : sIn( sFile ), fIn( sFile, File::Read ), bIn( fIn ), cBuf( 0 ), bAvail( false ), eMode( mRoot ), iLine( 1 ), iChar( 0 ), iDepth( 0 ) { } char nextChar() { if( bAvail ) return cBuf; if( bIn.read( &cBuf, 1 ) < 1 ) throw Bu::ExceptionBase("End of stream"); bAvail = true; if( cBuf == '\n' ) { iLine++; iChar = 0; } else iChar++; return cBuf; } TokType nextToken( Variant &v, Bu::String &sWsOut, int &iLineStart, int &iCharStart ) { Bu::String sTok, sWs; char buf; try { buf = nextChar(); } catch(...) { return tokEof; } for(;;) { if( buf == ' ' || buf == '\t' || buf == '\n' || buf == '\r' ) { sWs += buf; bAvail = false; } else break; try { buf = nextChar(); } catch(...) { sWsOut = sWs; return tokEof; } } sWsOut = sWs; iLineStart = iLine; iCharStart = iChar; bool bInStr = false; bool bDblStr; for(;;) { switch( eMode ) { case mRoot: if( buf == ' ' || buf == '\t' || buf == '\n' || buf == '\r' ) { if( sTok == "suite" ) return tokSuite; else { v = sTok; return tokFluff; } } else if( buf == '(' || buf == ')' || buf == '{' || buf == '}' || buf == ';' ) { if( sTok.getSize() == 0 ) { bAvail = false; v = buf; return tokChar; } else { v = sTok; return tokFluff; } } else { sTok += buf; bAvail = false; } break; case mSuite: if( buf == ' ' || buf == '\t' || buf == '\n' || buf == '\r' ) { if( sTok == "test" ) return tokTest; else if( sTok == "cleanup" ) return tokCleanup; else { v = sTok; return tokFluff; } } else if( buf == '(' || buf == ')' || buf == '}' || buf == ';' ) { if( sTok.getSize() == 0 ) { bAvail = false; v = buf; return tokChar; } else { v = sTok; return tokFluff; } } else if( buf == '{' ) { if( sTok.getSize() > 0 ) { v = sTok; return tokFluff; } else { sTok += buf; bAvail = false; eMode = mBlock; iDepth = 1; } } else { sTok += buf; bAvail = false; } break; case mBlock: if( bInStr ) { if( buf == '\\' ) { sTok += buf; bAvail = false; sTok += nextChar(); bAvail = false; } else if( bDblStr == true && buf == '\"' ) { sTok += buf; bAvail = false; bInStr = false; } else if( bDblStr == false && buf == '\'' ) { sTok += buf; bAvail = false; bInStr = false; } else { sTok += buf; bAvail = false; } } else { if( buf == '\"' ) { bInStr = true; bDblStr = true; sTok += buf; bAvail = false; } else if( buf == '\'' ) { bInStr = true; bDblStr = false; sTok += buf; bAvail = false; } else if( buf == '}' ) { sTok += buf; bAvail = false; iDepth--; if( iDepth == 0 ) { v = sTok; eMode = mSuite; return tokBlock; } } else if( buf == '{' ) { sTok += buf; bAvail = false; iDepth++; } else { sTok += buf; bAvail = false; } } break; } buf = nextChar(); } } void firstPass() { Variant v; Bu::String sWs; int iL, iC; for(;;) { TokType t = nextToken( v, sWs, iL, iC ); if( t == tokEof ) return; switch( eMode ) { case mRoot: if( t == tokSuite ) { if( nextToken( v, sWs, iL, iC ) != tokFluff ) throw Bu::ExceptionBase("%d:%d: Expected string " "following suite.", iL, iC ); s.sName = v.get(); if( nextToken( v, sWs, iL, iC ) != tokChar || v.get() != '{' ) throw Bu::ExceptionBase("%d:%d: Expected {, got " "'%s'", iL, iC, v.toString().getStr() ); eMode = mSuite; } break; case mSuite: switch( t ) { case tokFluff: break; case tokBlock: break; case tokTest: { if( nextToken( v, sWs, iL, iC ) != tokFluff ) throw Bu::ExceptionBase("%d:%d: Expected " "string following test.", iL, iC ); Test t; t.sName = v.get(); if( nextToken( v, sWs, iL, iC ) != tokBlock ) throw Bu::ExceptionBase("%d:%d: Expected " "{...} block.", iL, iC ); s.lTest.append( t ); } break; case tokCleanup: { if( nextToken( v, sWs, iL, iC ) != tokBlock ) throw Bu::ExceptionBase("%d:%d: Expected " "{...} block.", iL, iC ); } break; case tokChar: if( v.get() == '}' ) { eMode = mRoot; } else { } break; default: sio << iL << ":" << iC << ": Unexpected " << t << " found." << sio.nl; return; break; } break; default: sio << "???" << sio.nl; break; } } } void secondPass( const Bu::String &sOut ) { File fOut( sOut, File::WriteNew ); Formatter f( fOut ); fIn.setPos( 0 ); bIn.stop(); bIn.start(); bAvail = false; eMode = mRoot; iLine = 1; iChar = 0; bool bHasIncluded = false; Bu::String sWs; Variant v; int iL, iC; for(;;) { TokType t = nextToken( v, sWs, iL, iC ); switch( eMode ) { case mRoot: if( t == tokSuite ) { fOut.write( sWs ); if( nextToken( v, sWs, iL, iC ) != tokFluff ) throw Bu::ExceptionBase("%d:%d: Expected string " "following suite.", iL, iC ); s.sName = v.get(); if( nextToken( v, sWs, iL, iC ) != tokChar || v.get() != '{' ) throw Bu::ExceptionBase("%d:%d: Expected {", iL, iC ); eMode = mSuite; if( bHasIncluded == false ) { fOut.write("#include \n"); bHasIncluded = true; } Bu::String sClass = "_UnitSuite_" + s.sName; f << "class " << sClass << " : public Bu::UnitSuite" << f.nl << "{" << f.nl << "public:" << f.nl << "\t" << sClass << "()" << f.nl << "\t{" << f.nl << "\t\tsetName(\"" << s.sName << "\");" << f.nl; for( TestList::iterator i = s.lTest.begin(); i; i++ ) { f << "\t\tadd( static_cast(" "&" << sClass << "::_test_" << (*i).sName << "), \"" << (*i).sName << "\", Bu::UnitSuite::" "expectPass );" << f.nl; } f << "\t}" << f.nl << f.nl << "\tvirtual ~" << sClass << "() { }" << f.nl << f.nl; } else if( t == tokEof ) { Bu::String sClass = "_UnitSuite_" + s.sName; f << sWs << f.nl << "int main( int argc, char *argv[] )" << f.nl << "{" << f.nl << "\treturn " << sClass << "().run( argc, argv );" << f.nl << "}" << f.nl; } else { fOut.write( sWs ); f << v; } break; case mSuite: switch( t ) { case tokFluff: fOut.write( sWs ); fOut.write( v.get() ); break; case tokTest: { fOut.write( sWs ); if( nextToken( v, sWs, iL, iC ) != tokFluff ) throw Bu::ExceptionBase("%d:%d: Expected " "string following test.", iL, iC ); Test t; t.sName = v.get(); if( nextToken( v, sWs, iL, iC ) != tokBlock ) throw Bu::ExceptionBase("%d:%d: Expected " "{...} block.", iL, iC ); f << "void _test_" << t.sName << "()" << f.nl << "#line " << iL << " \"" << sIn << "\"" << f.nl << " " << v << f.nl; } break; case tokCleanup: { fOut.write( sWs ); if( nextToken( v, sWs, iL, iC ) != tokBlock ) throw Bu::ExceptionBase("%d:%d: Expected " "{...} block.", iL, iC ); f << "void cleanup()" << f.nl << "#line " << iL << " \"" << sIn << "\"" << f.nl << " " << v << f.nl; } break; case tokChar: if( v.get() == '}' ) { f << "};" << f.nl << f.nl; eMode = mRoot; } else { char buf = v.get(); fOut.write( sWs ); fOut.write( &buf, 1 ); } break; case tokBlock: fOut.write( sWs ); f << f.nl << "#line " << iL << " \"" << sIn << "\"" << f.nl; fOut.write( v.get() ); break; default: sio << iL << ":" << iC << ": Unexpected " << t << " found." << sio.nl; return; break; } break; default: sio << "???" << sio.nl; break; } if( t == tokEof ) return; } } private: Bu::String sIn; File fIn; Buffer bIn; char cBuf; bool bAvail; enum Mode { mRoot, mSuite, mBlock }; Mode eMode; int iLine, iChar; int iDepth; Suite s; }; int main( int argc, char *argv[] ) { if( argc < 3 ) { sio << "Too few parameters." << sio.nl; return 0; } Parser p( argv[1] ); p.firstPass(); p.secondPass( argv[2] ); }