#define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include #include "utils.h" #include #include #include #include #include "tap_file.h" #include ///////////////////////////////////////////////////////////////////////////////////////////////// // A very simple regex-based tokenizer ///////////////////////////////////////////////////////////////////////////////////////////////// enum class Skip_e { No = 0, Yes }; template class TokenDef_T { public: TokenDef_T(const char *aFilter, const tIdType aTokenId, const Skip_e aSkip) : mTokenId(aTokenId), mSkip(aSkip) { std::string Filter = "^"; Filter.append(aFilter); mFilter.assign(Filter); } const char *Match(const char *aFirst, const char *aEnd) const { std::match_results Result; bool RetVal = std::regex_search(aFirst, aEnd, Result, mFilter); if (RetVal) { return Result.suffix().first; } return nullptr; } bool Skip() const { return mSkip == Skip_e::Yes; } protected: std::regex mFilter; tIdType mTokenId; Skip_e mSkip; template friend class Token_t; }; template class Token_t { public: Token_t(const TokenDef_T &aTokenDef, const char *aFirst, const char *aLast) : mTokenId(aTokenDef.mTokenId), mValue(aFirst, aLast) {} explicit Token_t(const tIdType &aTokenId) : mTokenId(aTokenId) {} tIdType mTokenId; std::string mValue; }; enum class TokenTypes_e { String, Comment, Delimiter, Word, EndOfLine, Whitespace, Any, }; typedef Token_t Token_c; typedef Token_c(*PostProcess_f)(const TokenDef_T &aTokenDef, const char *aFirst, const char *aLast); class TokenDef_c : public TokenDef_T { public: TokenDef_c(const char *aFilter, const TokenTypes_e aTokenId, const Skip_e aSkip, PostProcess_f aPostProcess = DefaultPostProcess) : TokenDef_T(aFilter, aTokenId, aSkip), mPostPorcess(aPostProcess) {} Token_c PostProcess(const char *aFirst, const char *aLast) const { return mPostPorcess(*this, aFirst, aLast); } protected: static Token_c DefaultPostProcess(const TokenDef_T &aTokenDef, const char *aFirst, const char *aLast) { return Token_c(aTokenDef, aFirst, aLast); } PostProcess_f mPostPorcess; }; Token_c PostProcessString(const TokenDef_T &aTokenDef, const char *aFirst, const char *aLast) { ++aFirst; --aLast; Token_c RetVal(aTokenDef, aFirst, aLast); // Replace all "" sequences with a single " RetVal.mValue = std::regex_replace(RetVal.mValue, std::regex("\"\""), "\""); return RetVal; } TokenDef_c TokenDefs[] { { "[\r\t ]+", TokenTypes_e::Whitespace, Skip_e::Yes }, { "\"([^\"]|\"\")*\"", TokenTypes_e::String, Skip_e::No, PostProcessString }, { "#.*$", TokenTypes_e::Comment, Skip_e::No }, { "[;=]", TokenTypes_e::Delimiter, Skip_e::No }, { "[^ \r\t\n;=]+", TokenTypes_e::Word, Skip_e::No }, { ".", TokenTypes_e::Any, Skip_e::No } }; const char *TokenStr(TokenTypes_e aToken) { switch (aToken) { case TokenTypes_e::String: return "string"; case TokenTypes_e::Comment: return "comment"; case TokenTypes_e::Delimiter: return "delimiter"; case TokenTypes_e::Word: return "word"; case TokenTypes_e::EndOfLine: return "EOL"; case TokenTypes_e::Whitespace: return "Whitespace"; case TokenTypes_e::Any: return "any"; default: throw Generic_x() << "Unkown token type: " << size_t(aToken); } return nullptr; } std::string FormatErrorLine(const std::string& aLine, const char *TokenPtr, size_t aLineNo) { size_t Pos = TokenPtr - aLine.c_str(); std::stringstream Strm; Strm << "Syntax error at line: " << aLineNo << std::endl; Strm << aLine << std::endl; for (size_t i = 0; i < Pos; ++Pos) { Strm << ' '; } Strm << '^'; return Strm.str(); } std::string FormatErrorLine(const std::string& aLine, const char *TokenPtr) { size_t Pos = TokenPtr - aLine.c_str(); std::stringstream Strm; Strm << "Syntax error in command: " << std::endl; Strm << aLine << std::endl; for (size_t i = 0; i < Pos; ++Pos) { Strm << ' '; } Strm << '^'; return Strm.str(); } typedef std::vector TokenStream_t; ///////////////////////////////////////////////////////////////////////////////////////////////// // Utility functions ///////////////////////////////////////////////////////////////////////////////////////////////// static const int FileIdxEnd = -1; void SeekToFile(TapFile_c &aFile, int aFileIdx) { if (aFileIdx != FileIdxEnd && aFileIdx < 0) throw Generic_x() << "Invalid file index: " << DecPrinter(aFileIdx); aFile.SeekToBeginningOfTape(); int FileNo = 0; while ((aFileIdx == FileIdxEnd) || (aFileIdx != FileNo)) { if (aFile.GetState() == TapFile_c::State_e::EndOfTape) throw Generic_x() << "File with index " << DecPrinter(aFileIdx) << " doesn't exist"; do { std::vector Buffer; Buffer = aFile.Read(4096); // 4k records are the most common, so let's use that as the baseline } while (aFile.GetState() != TapFile_c::State_e::EndOfFile && aFile.GetState() != TapFile_c::State_e::EndOfTape); ++FileNo; } } int ParsePos(const std::string &aStrPos, bool aAllowSpecialValues = true) { if (aAllowSpecialValues && aStrPos == "end") return FileIdxEnd; try { int Pos = std::stoi(aStrPos, nullptr, 10); if (Pos < 0) throw Generic_x("Invalid position specified"); return Pos; } catch (std::out_of_range &) { throw Generic_x("Invalid position specified"); } } void Add(std::ifstream &aSrc, TapFile_c &aTarget, size_t aRecordSize = 4096) { std::vector Buffer(aRecordSize); while (!aSrc.eof()) { aSrc.read((char*)(&Buffer[0]), aRecordSize); size_t ReadCount = size_t(aSrc.gcount()); if (ReadCount != aRecordSize && !aSrc.eof()) throw Generic_x() << "Can't read from input file"; if (ReadCount > 0) { Buffer.resize(ReadCount); aTarget.WriteRecord(Buffer); } } aTarget.WriteEndOfFile(); aTarget.flush(); } void Add(TapFile_c &aSrc, TapFile_c &aTarget) { std::vector Buffer; bool NeedEOF = false; bool First = true; do { Buffer = aSrc.Read(); if (aSrc.GetState() == TapFile_c::State_e::EndOfTape) { if (First) return; throw Generic_x() << "Unexpected end of input file"; } First = false; if (aSrc.GetState() == TapFile_c::State_e::EndOfFile) break; aTarget.WriteRecord(Buffer); NeedEOF = true; } while (true); if (NeedEOF) aTarget.WriteEndOfFile(); aTarget.flush(); } void Add(TapFile_c &aSrc, std::ofstream &aTarget) { std::vector Buffer; bool First = true; do { Buffer = aSrc.Read(); if (aSrc.GetState() == TapFile_c::State_e::EndOfTape) { if (First) return; throw Generic_x() << "Unexpected end of input file"; } First = false; if (aSrc.GetState() == TapFile_c::State_e::EndOfFile) break; aTarget.write((char*)(&Buffer[0]), Buffer.size()); if (!aTarget.good()) throw Generic_x() << "Can't write to file"; } while (true); aTarget.flush(); } void Skip(TapFile_c &aSrc) { std::vector Buffer; bool First = true; do { aSrc.SeekToNextRecord(); if (aSrc.GetState() == TapFile_c::State_e::EndOfTape) { if (First) return; throw Generic_x() << "Unexpected end of input file"; } First = false; if (aSrc.GetState() == TapFile_c::State_e::EndOfFile) break; } while (true); } ///////////////////////////////////////////////////////////////////////////////////////////////// // Commands ///////////////////////////////////////////////////////////////////////////////////////////////// class CmdFactoryBase_i { public: virtual ~CmdFactoryBase_i() {} virtual std::unique_ptr Parse(TokenStream_t::const_iterator &aBegin, TokenStream_t::const_iterator aEnd) = 0; virtual std::string Help() const = 0; }; class CmdBase_i { public: virtual ~CmdBase_i() {} virtual void Exec(TapFile_c &aTarget) = 0; }; class CmdLs_c : public CmdFactoryBase_i { public: virtual ~CmdLs_c() {} virtual std::unique_ptr Parse(TokenStream_t::const_iterator &aBegin, TokenStream_t::const_iterator aEnd) override { if (aBegin->mValue != "ls") return nullptr; ++aBegin; if (aBegin != aEnd && aBegin->mTokenId != TokenTypes_e::EndOfLine) return nullptr; return std::make_unique(); } virtual std::string Help() const override { return "ls - Lists the content of the tape file"; } class Exec_c : public CmdBase_i { virtual void Exec(TapFile_c &aTarget) override { std::cout << "TAP FILE: " << aTarget.GetFileName() << std::endl; if (!aTarget.good()) { std::cout << " File empty" << std::endl; return; } aTarget.SeekToBeginningOfTape(); size_t FileNo = 0; while (aTarget.GetState() != TapFile_c::State_e::EndOfTape) { size_t FileSize = 0; size_t RecCnt = 1; do { std::vector Buffer; Buffer = aTarget.Read(); if (aTarget.GetState() == TapFile_c::State_e::EndOfTape) break; FileSize += Buffer.size(); if (aTarget.GetState() == TapFile_c::State_e::EndOfRecord) ++RecCnt; } while (aTarget.GetState() != TapFile_c::State_e::EndOfFile && aTarget.GetState() != TapFile_c::State_e::EndOfTape); if (aTarget.GetState() != TapFile_c::State_e::EndOfTape) { std::cout << " File: " << DecPrinter(FileNo) << " size: " << DecPrinter(FileSize) << " record count: " << DecPrinter(RecCnt) << std::endl; } ++FileNo; } } }; }; // - extract class CmdExtract_c : public CmdFactoryBase_i { public: virtual ~CmdExtract_c() override {} virtual std::unique_ptr Parse(TokenStream_t::const_iterator &aBegin, TokenStream_t::const_iterator aEnd) override { // format of the command: // extract if (aBegin->mValue != "extract") return nullptr; ++aBegin; if (aBegin == aEnd) return nullptr; int Pos = ParsePos(aBegin->mValue, false); ++aBegin; if (aBegin == aEnd) return nullptr; std::string FileName = aBegin->mValue; ++aBegin; if (aBegin != aEnd && aBegin->mTokenId != TokenTypes_e::EndOfLine) return nullptr; // TODO: add warning about existing file return std::make_unique(FileName, Pos); } virtual std::string Help() const override { return "extract - Extracts the file at (0-based) position into file."; } class Exec_c : public CmdBase_i { public: Exec_c(std::string aFileName, int aPos) : mFileName(aFileName), mPos(aPos) {} virtual void Exec(TapFile_c &aTarget) override { SeekToFile(aTarget, mPos); std::vector Buffer(mRecordSize); std::ofstream File(mFileName, std::ios_base::out | std::ios_base::binary); if (!File.good()) throw Generic_x() << "Can't open file: " << mFileName; Add(aTarget, File); } std::string mFileName; int mPos; size_t mRecordSize; }; }; // - replace (uses temp files) // - delete (uses temp files) // - insert [] (uses temp files) class CmdReplaceInsertDelete_c : public CmdFactoryBase_i { public: virtual ~CmdReplaceInsertDelete_c() override {} virtual std::unique_ptr Parse(TokenStream_t::const_iterator &aBegin, TokenStream_t::const_iterator aEnd) override { // format of the command: // extract if (aBegin->mValue == "replace") { ++aBegin; if (aBegin == aEnd) return nullptr; int Pos = ParsePos(aBegin->mValue); ++aBegin; if (aBegin == aEnd) return nullptr; std::string FileName = aBegin->mValue; ++aBegin; if (aBegin != aEnd && aBegin->mTokenId != TokenTypes_e::EndOfLine) return nullptr; return std::make_unique(FileName, Pos, Mode_e::Replace); } else if (aBegin->mValue == "delete") { ++aBegin; if (aBegin == aEnd) return nullptr; int Pos = ParsePos(aBegin->mValue, false); ++aBegin; if (aBegin != aEnd && aBegin->mTokenId != TokenTypes_e::EndOfLine) return nullptr; return std::make_unique(Pos, Mode_e::Delete); } else if (aBegin->mValue == "insert") { ++aBegin; if (aBegin == aEnd) return nullptr; std::string FileName = aBegin->mValue; ++aBegin; if (aBegin == aEnd) return nullptr; int Pos = ParsePos(aBegin->mValue); ++aBegin; if (aBegin != aEnd && aBegin->mTokenId != TokenTypes_e::EndOfLine) return nullptr; return std::make_unique(FileName, Pos, Mode_e::Insert); } else { return nullptr; } } virtual std::string Help() const override { return "insert - Inserts the file at (0-based) position. \n" " Position can be specified as 'end' in which case file gets inserted at the end of the tape\n" "replace - Replaces file at (0-based) position.\n" "delete - Delete file at (0-based) position.\n" " Position can be specified as 'end' in which case file gets inserted at the end of the tape"; } enum class Mode_e { Replace = 0, Delete, Insert }; class Exec_c : public CmdBase_i { public: Exec_c(std::string aFileName, int aPos, Mode_e aMode) : mFileName(aFileName), mPos(aPos), mMode(aMode) {} Exec_c(int aPos, Mode_e aMode) : mPos(aPos), mMode(aMode) {} virtual void Exec(TapFile_c &aTarget) override { aTarget.SeekToBeginningOfTape(); boost::filesystem::path TempFile = boost::filesystem::unique_path(); std::ifstream File; if (mMode != Mode_e::Delete) { File.open(mFileName, std::ios_base::in | std::ios_base::binary); if (!File.good()) throw Generic_x() << "Can't open file: " << mFileName; } TapFile_c TempTape(TempFile.string()); for (int i = 0; i < mPos || mPos == FileIdxEnd; ++i) { Add(aTarget, TempTape); if (aTarget.GetState() == TapFile_c::State_e::EndOfTape) break; } if (mMode == Mode_e::Insert || mMode == Mode_e::Replace) { Add(File, TempTape); } if (aTarget.GetState() != TapFile_c::State_e::EndOfTape) { if (mMode == Mode_e::Delete || mMode == Mode_e::Replace) { Skip(aTarget); } } while (aTarget.GetState() != TapFile_c::State_e::EndOfTape) { Add(aTarget, TempTape); } TempTape.close(); File.close(); aTarget.close(); boost::filesystem::rename(TempFile, aTarget.GetFileName()); aTarget.ReOpen(); } std::string mFileName; int mPos; size_t mRecordSize; Mode_e mMode; }; }; const std::unique_ptr Commands[] = { std::make_unique(), std::make_unique(), std::make_unique() }; ///////////////////////////////////////////////////////////////////////////////////////////////// // Main ///////////////////////////////////////////////////////////////////////////////////////////////// int PrintUsage(const char *aExecName, const char *ErrorStr = nullptr) { std::cout << "Usage: " << aExecName << " [-s