Loading commands/text/tr.cpp +178 −57 Changes for commands/text/tr.cpp: 178 added lines, 57 removed lines. Original line number Diff line number Diff line Loading @@ -29,96 +29,217 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <string> #include <vector> #include <unistd.h> #include <cerrno> #include <cctype> #include <cstdlib> #include "shell.h" namespace fresh { namespace cmd { // "tr SET1 SET2" (translate) or "tr -d SET1" (delete): reads stdin, // writes stdout. SETs support "a-z"-style ranges and "\n"/"\t"/"\\" // escapes; no [:class:]/[=equiv=] classes, no "-s" squeeze, no // "-c"/"-C" complement -- covers the common byte-translation/deletion // idiom (e.g. "tr A-Z a-z", "tr -d '\r'") without a full POSIX tr. // POSIX tr: tr [-c|-C] [-s] [-t] SET1 SET2, tr [-c] -d [-s] SET1 [SET2], // tr [-c] -s SET1. Sets take escapes (\\ \a \b \f \n \r \t \v \NNN), // ranges a-z, [:class:], [=c=], and in SET2 [c*n] / [c*] (fill). class TrCommand : public ShellCommand { public: TrCommand():ShellCommand("tr"){} static std::string expandSet(const std::string &spec){ std::string out; for(size_t i=0;i<spec.size();++i){ char c=spec[i]; if(c=='\\' && i+1<spec.size()){ char e=spec[++i]; private: // one set; fillAt: index of a "[c*]" element (SET2 only), -1 if none static bool expandSet(const std::string &spec,std::vector<unsigned char> &out, long &fillAt,unsigned char &fillChar,std::string &err){ fillAt=-1; size_t i=0,n=spec.size(); auto readChar=[&](size_t &k)->int{ if(k>=n) return -1; unsigned char c=(unsigned char)spec[k++]; if(c!='\\' || k>=n) return c; unsigned char e=(unsigned char)spec[k++]; switch(e){ case 'n': out+='\n'; break; case 't': out+='\t'; break; case 'r': out+='\r'; break; case '\\': out+='\\'; break; default: out+=e; break; case 'a': return '\a'; case 'b': return '\b'; case 'f': return '\f'; case 'n': return '\n'; case 'r': return '\r'; case 't': return '\t'; case 'v': return '\v'; case '\\': return '\\'; default: if(e>='0' && e<='7'){ int v=e-'0'; for(int d=0;d<2 && k<n && spec[k]>='0' && spec[k]<='7';++d) v=v*8+(spec[k++]-'0'); return v & 0xff; } return e; } }; while(i<n){ if(spec[i]=='[' && i+1<n && spec[i+1]==':'){ size_t e=spec.find(":]",i+2); if(e!=std::string::npos){ std::string cls=spec.substr(i+2,e-i-2); int (*fn)(int)=nullptr; if(cls=="alpha") fn=isalpha; else if(cls=="digit") fn=isdigit; else if(cls=="alnum") fn=isalnum; else if(cls=="space") fn=isspace; else if(cls=="upper") fn=isupper; else if(cls=="lower") fn=islower; else if(cls=="punct") fn=ispunct; else if(cls=="xdigit") fn=isxdigit; else if(cls=="cntrl") fn=iscntrl; else if(cls=="print") fn=isprint; else if(cls=="graph") fn=isgraph; else if(cls=="blank") fn=isblank; else { err="invalid character class '"+cls+"'"; return false; } for(int c=0;c<256;++c) if(fn(c)) out.push_back((unsigned char)c); i=e+2; continue; } if(i+2<spec.size() && spec[i+1]=='-'){ unsigned char lo=(unsigned char)c,hi=(unsigned char)spec[i+2]; if(lo<=hi){ for(unsigned char v=lo; v<=hi; ++v){ out+=(char)v; if(v==255) break; } i+=2; if(spec[i]=='[' && i+1<n && spec[i+1]=='='){ size_t k=i+2; int c=readChar(k); if(c>=0 && k+1<n && spec[k]=='=' && spec[k+1]==']'){ out.push_back((unsigned char)c); i=k+2; continue; } } out+=c; if(spec[i]=='['){ // [c*n] / [c*] size_t k=i+1; int c=readChar(k); if(c>=0 && k<n && spec[k]=='*'){ size_t close=spec.find(']',k+1); if(close!=std::string::npos){ std::string cnt=spec.substr(k+1,close-k-1); bool digits=true; for(char d: cnt) if(!isdigit((unsigned char)d)) digits=false; if(digits){ long times=cnt.empty() ? 0 : strtol(cnt.c_str(),nullptr,cnt[0]=='0' ? 8 : 10); if(times==0){ fillAt=(long)out.size(); fillChar=(unsigned char)c; } else for(long t=0;t<times;++t) out.push_back((unsigned char)c); i=close+1; continue; } } } } return out; size_t k=i; int lo=readChar(k); if(k<n && spec[k]=='-' && k+1<n){ size_t k2=k+1; int hi=readChar(k2); if(hi<lo){ err="range-endpoints are in reverse collating sequence order"; return false; } for(int c=lo;c<=hi;++c) out.push_back((unsigned char)c); i=k2; continue; } out.push_back((unsigned char)lo); i=k; } return true; } struct Output { std::string buf; void put(unsigned char c){ buf+=(char)c; if(buf.size()>=65536) flush(); } void flush(){ size_t off=0; while(off<buf.size()){ ssize_t w=::write(1,buf.data()+off,buf.size()-off); if(w<0){ if(errno==EINTR) continue; break; } off+=(size_t)w; } buf.clear(); } }; public: int runCommand(int argc,const char **argv){ bool del=false; bool complement=false,del=false,squeeze=false,truncate=false; std::vector<std::string> operands; bool endOfOpts=false; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-d") del=true; else operands.push_back(a); if(endOfOpts || a.size()<2 || a[0]!='-'){ operands.push_back(a); continue; } if(a=="--"){ endOfOpts=true; continue; } if(a=="--complement"){ complement=true; continue; } if(a=="--delete"){ del=true; continue; } if(a=="--squeeze-repeats"){ squeeze=true; continue; } if(a=="--truncate-set1"){ truncate=true; continue; } for(size_t k=1;k<a.size();++k){ switch(a[k]){ case 'c': case 'C': complement=true; break; case 'd': del=true; break; case 's': squeeze=true; break; case 't': truncate=true; break; default: std::cerr << "tr: invalid option -- '" << a[k] << "'" << std::endl; return 1; } } } size_t need=(del && !squeeze) || (squeeze && !del && operands.size()==1) ? 1 : 2; if(operands.size()<need || operands.size()>2 || (del && !squeeze && operands.size()!=1)){ std::cerr << "tr: " << (operands.size()<need ? "missing operand" : "extra operand") << std::endl; return 1; } if(del){ if(operands.empty()){ std::cerr << "tr: missing operand" << std::endl; std::string err; std::vector<unsigned char> set1,set2; long fill1,fill2; unsigned char fc1=0,fc2=0; if(!expandSet(operands[0],set1,fill1,fc1,err) || (operands.size()>1 && !expandSet(operands[1],set2,fill2,fc2,err))){ std::cerr << "tr: " << err << std::endl; return 1; } std::string set=expandSet(operands[0]); bool table[256]={false}; for(unsigned char c: set) table[c]=true; char ch; while(std::cin.get(ch)) if(!table[(unsigned char)ch]) std::cout.put(ch); return 0; if(complement){ bool in[256]={false}; for(unsigned char c: set1) in[c]=true; set1.clear(); for(int c=0;c<256;++c) if(!in[c]) set1.push_back((unsigned char)c); } if(operands.size()<2){ std::cerr << "tr: usage: tr SET1 SET2" << std::endl; return 1; bool delSet[256]={false},sqSet[256]={false}; unsigned char map[256]; for(int c=0;c<256;++c) map[c]=(unsigned char)c; bool translate=!del && operands.size()==2; if(del) for(unsigned char c: set1) delSet[c]=true; if(translate){ if(operands.size()>1 && fill2>=0){ // [c*]: pad SET2 to SET1's length at that position long pad=(long)set1.size()-(long)set2.size(); if(pad>0) set2.insert(set2.begin()+fill2,(size_t)pad,fc2); } std::string set1=expandSet(operands[0]); std::string set2=expandSet(operands[1]); if(set2.empty()){ std::cerr << "tr: SET2 must not be empty" << std::endl; if(truncate && set1.size()>set2.size()) set1.resize(set2.size()); if(set2.empty() && !set1.empty()){ std::cerr << "tr: when not truncating set1, string2 must be non-empty" << std::endl; return 1; } char map[256]; for(int i=0;i<256;++i) map[i]=(char)i; for(size_t i=0;i<set1.size();++i){ unsigned char from=(unsigned char)set1[i]; unsigned char to=(unsigned char)set2[i<set2.size() ? i : set2.size()-1]; map[from]=(char)to; for(size_t i=0;i<set1.size();++i) map[set1[i]]=set2[i<set2.size() ? i : set2.size()-1]; } if(squeeze){ const std::vector<unsigned char> &sq=(operands.size()==2) ? set2 : set1; for(unsigned char c: sq) sqSet[c]=true; } char ch; while(std::cin.get(ch)) std::cout.put(map[(unsigned char)ch]); Output out; unsigned char buf[65536]; int last=-1; for(;;){ ssize_t n=::read(0,buf,sizeof buf); if(n<0){ if(errno==EINTR) continue; break; } if(n==0) break; for(ssize_t k=0;k<n;++k){ unsigned char c=buf[k]; if(del && delSet[c]) continue; c=map[c]; if(squeeze && sqSet[c] && last==(int)c) continue; out.put(c); last=c; } } out.flush(); return 0; } }; Loading Loading
commands/text/tr.cpp +178 −57 Changes for commands/text/tr.cpp: 178 added lines, 57 removed lines. Original line number Diff line number Diff line Loading @@ -29,96 +29,217 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <string> #include <vector> #include <unistd.h> #include <cerrno> #include <cctype> #include <cstdlib> #include "shell.h" namespace fresh { namespace cmd { // "tr SET1 SET2" (translate) or "tr -d SET1" (delete): reads stdin, // writes stdout. SETs support "a-z"-style ranges and "\n"/"\t"/"\\" // escapes; no [:class:]/[=equiv=] classes, no "-s" squeeze, no // "-c"/"-C" complement -- covers the common byte-translation/deletion // idiom (e.g. "tr A-Z a-z", "tr -d '\r'") without a full POSIX tr. // POSIX tr: tr [-c|-C] [-s] [-t] SET1 SET2, tr [-c] -d [-s] SET1 [SET2], // tr [-c] -s SET1. Sets take escapes (\\ \a \b \f \n \r \t \v \NNN), // ranges a-z, [:class:], [=c=], and in SET2 [c*n] / [c*] (fill). class TrCommand : public ShellCommand { public: TrCommand():ShellCommand("tr"){} static std::string expandSet(const std::string &spec){ std::string out; for(size_t i=0;i<spec.size();++i){ char c=spec[i]; if(c=='\\' && i+1<spec.size()){ char e=spec[++i]; private: // one set; fillAt: index of a "[c*]" element (SET2 only), -1 if none static bool expandSet(const std::string &spec,std::vector<unsigned char> &out, long &fillAt,unsigned char &fillChar,std::string &err){ fillAt=-1; size_t i=0,n=spec.size(); auto readChar=[&](size_t &k)->int{ if(k>=n) return -1; unsigned char c=(unsigned char)spec[k++]; if(c!='\\' || k>=n) return c; unsigned char e=(unsigned char)spec[k++]; switch(e){ case 'n': out+='\n'; break; case 't': out+='\t'; break; case 'r': out+='\r'; break; case '\\': out+='\\'; break; default: out+=e; break; case 'a': return '\a'; case 'b': return '\b'; case 'f': return '\f'; case 'n': return '\n'; case 'r': return '\r'; case 't': return '\t'; case 'v': return '\v'; case '\\': return '\\'; default: if(e>='0' && e<='7'){ int v=e-'0'; for(int d=0;d<2 && k<n && spec[k]>='0' && spec[k]<='7';++d) v=v*8+(spec[k++]-'0'); return v & 0xff; } return e; } }; while(i<n){ if(spec[i]=='[' && i+1<n && spec[i+1]==':'){ size_t e=spec.find(":]",i+2); if(e!=std::string::npos){ std::string cls=spec.substr(i+2,e-i-2); int (*fn)(int)=nullptr; if(cls=="alpha") fn=isalpha; else if(cls=="digit") fn=isdigit; else if(cls=="alnum") fn=isalnum; else if(cls=="space") fn=isspace; else if(cls=="upper") fn=isupper; else if(cls=="lower") fn=islower; else if(cls=="punct") fn=ispunct; else if(cls=="xdigit") fn=isxdigit; else if(cls=="cntrl") fn=iscntrl; else if(cls=="print") fn=isprint; else if(cls=="graph") fn=isgraph; else if(cls=="blank") fn=isblank; else { err="invalid character class '"+cls+"'"; return false; } for(int c=0;c<256;++c) if(fn(c)) out.push_back((unsigned char)c); i=e+2; continue; } if(i+2<spec.size() && spec[i+1]=='-'){ unsigned char lo=(unsigned char)c,hi=(unsigned char)spec[i+2]; if(lo<=hi){ for(unsigned char v=lo; v<=hi; ++v){ out+=(char)v; if(v==255) break; } i+=2; if(spec[i]=='[' && i+1<n && spec[i+1]=='='){ size_t k=i+2; int c=readChar(k); if(c>=0 && k+1<n && spec[k]=='=' && spec[k+1]==']'){ out.push_back((unsigned char)c); i=k+2; continue; } } out+=c; if(spec[i]=='['){ // [c*n] / [c*] size_t k=i+1; int c=readChar(k); if(c>=0 && k<n && spec[k]=='*'){ size_t close=spec.find(']',k+1); if(close!=std::string::npos){ std::string cnt=spec.substr(k+1,close-k-1); bool digits=true; for(char d: cnt) if(!isdigit((unsigned char)d)) digits=false; if(digits){ long times=cnt.empty() ? 0 : strtol(cnt.c_str(),nullptr,cnt[0]=='0' ? 8 : 10); if(times==0){ fillAt=(long)out.size(); fillChar=(unsigned char)c; } else for(long t=0;t<times;++t) out.push_back((unsigned char)c); i=close+1; continue; } } } } return out; size_t k=i; int lo=readChar(k); if(k<n && spec[k]=='-' && k+1<n){ size_t k2=k+1; int hi=readChar(k2); if(hi<lo){ err="range-endpoints are in reverse collating sequence order"; return false; } for(int c=lo;c<=hi;++c) out.push_back((unsigned char)c); i=k2; continue; } out.push_back((unsigned char)lo); i=k; } return true; } struct Output { std::string buf; void put(unsigned char c){ buf+=(char)c; if(buf.size()>=65536) flush(); } void flush(){ size_t off=0; while(off<buf.size()){ ssize_t w=::write(1,buf.data()+off,buf.size()-off); if(w<0){ if(errno==EINTR) continue; break; } off+=(size_t)w; } buf.clear(); } }; public: int runCommand(int argc,const char **argv){ bool del=false; bool complement=false,del=false,squeeze=false,truncate=false; std::vector<std::string> operands; bool endOfOpts=false; for(int i=1;i<argc;++i){ std::string a=argv[i]; if(a=="-d") del=true; else operands.push_back(a); if(endOfOpts || a.size()<2 || a[0]!='-'){ operands.push_back(a); continue; } if(a=="--"){ endOfOpts=true; continue; } if(a=="--complement"){ complement=true; continue; } if(a=="--delete"){ del=true; continue; } if(a=="--squeeze-repeats"){ squeeze=true; continue; } if(a=="--truncate-set1"){ truncate=true; continue; } for(size_t k=1;k<a.size();++k){ switch(a[k]){ case 'c': case 'C': complement=true; break; case 'd': del=true; break; case 's': squeeze=true; break; case 't': truncate=true; break; default: std::cerr << "tr: invalid option -- '" << a[k] << "'" << std::endl; return 1; } } } size_t need=(del && !squeeze) || (squeeze && !del && operands.size()==1) ? 1 : 2; if(operands.size()<need || operands.size()>2 || (del && !squeeze && operands.size()!=1)){ std::cerr << "tr: " << (operands.size()<need ? "missing operand" : "extra operand") << std::endl; return 1; } if(del){ if(operands.empty()){ std::cerr << "tr: missing operand" << std::endl; std::string err; std::vector<unsigned char> set1,set2; long fill1,fill2; unsigned char fc1=0,fc2=0; if(!expandSet(operands[0],set1,fill1,fc1,err) || (operands.size()>1 && !expandSet(operands[1],set2,fill2,fc2,err))){ std::cerr << "tr: " << err << std::endl; return 1; } std::string set=expandSet(operands[0]); bool table[256]={false}; for(unsigned char c: set) table[c]=true; char ch; while(std::cin.get(ch)) if(!table[(unsigned char)ch]) std::cout.put(ch); return 0; if(complement){ bool in[256]={false}; for(unsigned char c: set1) in[c]=true; set1.clear(); for(int c=0;c<256;++c) if(!in[c]) set1.push_back((unsigned char)c); } if(operands.size()<2){ std::cerr << "tr: usage: tr SET1 SET2" << std::endl; return 1; bool delSet[256]={false},sqSet[256]={false}; unsigned char map[256]; for(int c=0;c<256;++c) map[c]=(unsigned char)c; bool translate=!del && operands.size()==2; if(del) for(unsigned char c: set1) delSet[c]=true; if(translate){ if(operands.size()>1 && fill2>=0){ // [c*]: pad SET2 to SET1's length at that position long pad=(long)set1.size()-(long)set2.size(); if(pad>0) set2.insert(set2.begin()+fill2,(size_t)pad,fc2); } std::string set1=expandSet(operands[0]); std::string set2=expandSet(operands[1]); if(set2.empty()){ std::cerr << "tr: SET2 must not be empty" << std::endl; if(truncate && set1.size()>set2.size()) set1.resize(set2.size()); if(set2.empty() && !set1.empty()){ std::cerr << "tr: when not truncating set1, string2 must be non-empty" << std::endl; return 1; } char map[256]; for(int i=0;i<256;++i) map[i]=(char)i; for(size_t i=0;i<set1.size();++i){ unsigned char from=(unsigned char)set1[i]; unsigned char to=(unsigned char)set2[i<set2.size() ? i : set2.size()-1]; map[from]=(char)to; for(size_t i=0;i<set1.size();++i) map[set1[i]]=set2[i<set2.size() ? i : set2.size()-1]; } if(squeeze){ const std::vector<unsigned char> &sq=(operands.size()==2) ? set2 : set1; for(unsigned char c: sq) sqSet[c]=true; } char ch; while(std::cin.get(ch)) std::cout.put(map[(unsigned char)ch]); Output out; unsigned char buf[65536]; int last=-1; for(;;){ ssize_t n=::read(0,buf,sizeof buf); if(n<0){ if(errno==EINTR) continue; break; } if(n==0) break; for(ssize_t k=0;k<n;++k){ unsigned char c=buf[k]; if(del && delSet[c]) continue; c=map[c]; if(squeeze && sqSet[c] && last==(int)c) continue; out.put(c); last=c; } } out.flush(); return 0; } }; Loading