Commit 943fa751 authored by jan.koester's avatar jan.koester
Browse files

test

parent 7670526f
Loading
Loading
Loading
Loading
+13 −3
Changes for commands/shell/for.cpp: 13 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -46,14 +46,19 @@ namespace fresh { namespace cmd {
            std::string remainder=(argc>1 && argv[1]) ? argv[1] : "";
            std::vector<ShellWord> words=tokenizeShellWords(remainder);

            if(words.size()<2 || words[1].text!="in"){
            // "for VAR; do" / "for VAR do" (no "in" list): the loop runs
            // over the positional parameters, "$@" (the kernel's
            // scripts/setlocalversion uses "for file; do")
            bool overArgs=words.size()>=2 && words[1].text!="in" &&
                          (words[1].text==";" || words[1].text=="do");
            if(words.size()<2 || (words[1].text!="in" && !overArgs)){
                std::cerr << "for: expected 'VAR in ...'" << std::endl;
                return 2;
            }

            std::string varname=words[0].text;

            size_t doIdx=findShellKeyword(words,2,words.size(),"do");
            size_t doIdx=findShellKeyword(words,overArgs ? 1 : 2,words.size(),"do");
            if(doIdx==words.size()){
                std::cerr << "for: expected 'do'" << std::endl;
                return 2;
@@ -65,11 +70,16 @@ namespace fresh { namespace cmd {
                return 2;
            }

            std::vector<std::string> items;
            if(overArgs){
                items=Environment::instance().positionalArgs();
            }else{
                size_t itemsEnd=doIdx;
                if(itemsEnd>2 && words[itemsEnd-1].text==";")
                    --itemsEnd;
                std::string itemtext=shellWordsRawText(remainder,words,2,itemsEnd);
            std::vector<std::string> items=tokenizeShellArgs(itemtext);
                items=tokenizeShellArgs(itemtext);
            }

            std::string body=shellWordsRawText(remainder,words,doIdx+1,doneIdx);
            std::string trailing=shellWordsRawText(remainder,words,doneIdx+1,words.size());
+80 −121
Changes for src/shell.cpp: 80 added lines, 121 removed lines.
Original line number Diff line number Diff line
@@ -80,6 +80,70 @@ static bool isValidShellName(const std::string &s){
    return true;
}


// The ')' closing a "$(" whose inner text starts at `from` (npos-safe:
// s.size() when unterminated). Quoted text, backslash escapes, backticks
// and nested "$(" are skipped, and a case pattern's unbalanced ')'
// ("case x in a) ...;; esac") does not close anything -- counting bare
// parens instead ended the kernel's
//   $(ld -v | sed -e 's/(compatible with [^)]*)//')
// at the ')' inside the sed program.
static size_t cmdSubstClose(const std::string &s,size_t from){
    size_t n=s.size(),i=from;
    int depth=1;
    std::vector<int> caseAt;          // paren depth at each open "case"
    auto wordAt=[&](size_t k,const char *w){
        size_t l=strlen(w);
        if(s.compare(k,l,w)!=0) return false;
        bool startOk=(k==0) || strchr(" \t\n;&|(",s[k-1]);
        bool endOk=(k+l>=n) || strchr(" \t\n;&|)",s[k+l]);
        return startOk && endOk;
    };
    while(i<n){
        char c=s[i];
        if(c=='\\'){ i+=2; continue; }
        if(c=='\''){
            size_t e=s.find('\'',i+1);
            i=(e==std::string::npos) ? n : e+1;
            continue;
        }
        if(c=='"'){
            size_t k=i+1;
            while(k<n && s[k]!='"'){
                if(s[k]=='\\') { k+=2; continue; }
                if(s[k]=='$' && k+1<n && s[k+1]=='('){ k=cmdSubstClose(s,k+2)+1; continue; }
                if(s[k]=='`'){ size_t e=s.find('`',k+1); k=(e==std::string::npos) ? n : e+1; continue; }
                k++;
            }
            i=k+1;
            continue;
        }
        if(c=='`'){
            size_t k=i+1;
            while(k<n && s[k]!='`'){ if(s[k]=='\\') k++; k++; }
            i=k+1;
            continue;
        }
        if(c=='#' && (i==from || strchr(" \t\n;",s[i-1]))){
            size_t e=s.find('\n',i);
            i=(e==std::string::npos) ? n : e;
            continue;
        }
        if(c=='$' && i+1<n && s[i+1]=='('){ i=cmdSubstClose(s,i+2)+1; continue; }
        if(wordAt(i,"case")){ caseAt.push_back(depth); i+=4; continue; }
        if(wordAt(i,"esac") && !caseAt.empty()){ caseAt.pop_back(); i+=4; continue; }
        if(c=='('){ depth++; i++; continue; }
        if(c==')'){
            if(!caseAt.empty() && caseAt.back()==depth){ i++; continue; }   // a case pattern
            if(--depth==0) return i;
            i++;
            continue;
        }
        i++;
    }
    return n;
}

static void pushGlobExpanded(std::vector<std::string> &tokens,const std::string &word){
    if(word.find_first_of("*?[")==std::string::npos){
        tokens.push_back(word);
@@ -526,13 +590,7 @@ std::vector<std::string> fresh::tokenizeShellArgs(const std::string &segment){
                    ++j;
                }else if(cj=='$' && j+1<n && segment[j+1]=='(' &&
                         !(j+2<n && segment[j+2]=='(')){
                    size_t k=j+2;
                    int pdepth=1;
                    while(k<n && pdepth>0){
                        if(segment[k]=='(') ++pdepth;
                        else if(segment[k]==')') --pdepth;
                        if(pdepth>0) ++k;
                    }
                    size_t k=cmdSubstClose(segment,j+2);
                    raw+=Shell::instance().runCaptured(segment.substr(j+2,k-(j+2)));
                    j=(k<n) ? k+1 : k;
                }else if(cj=='`'){
@@ -610,13 +668,7 @@ std::vector<std::string> fresh::tokenizeShellArgs(const std::string &segment){
        // as the rest of this tokenizer; a "$(...)" written inside "..."
        // is not expanded, just copied like any other quoted text.
        if(c=='$' && i+1<n && segment[i+1]=='(' && !(i+2<n && segment[i+2]=='(')){
            size_t j=i+2;
            int depth=1;
            while(j<n && depth>0){
                if(segment[j]=='(') ++depth;
                else if(segment[j]==')') --depth;
                if(depth>0) ++j;
            }
            size_t j=cmdSubstClose(segment,i+2);
            std::string inner=segment.substr(i+2,j-(i+2));
            flushPending();
            current+=Shell::instance().runCaptured(inner);
@@ -767,13 +819,7 @@ std::vector<std::string> fresh::tokenizeShellArgs(const std::string &segment){
                            ++j;
                        }else if(cj=='$' && j+1<n && segment[j+1]=='(' &&
                                 !(j+2<n && segment[j+2]=='(')){
                            size_t k=j+2;
                            int pdepth=1;
                            while(k<n && pdepth>0){
                                if(segment[k]=='(') ++pdepth;
                                else if(segment[k]==')') --pdepth;
                                if(pdepth>0) ++k;
                            }
                            size_t k=cmdSubstClose(segment,j+2);
                            rawExpr+=Shell::instance().runCaptured(segment.substr(j+2,k-(j+2)));
                            j=(k<n) ? k+1 : k;
                        }else if(cj=='\\' && j+1<n && segment[j+1]=='`'){
@@ -827,13 +873,7 @@ std::vector<std::string> fresh::tokenizeShellArgs(const std::string &segment){
                // limit the top-level "$(...)" handling already has.
                if(segment[i]=='$' && i+1<n && segment[i+1]=='(' &&
                   !(i+2<n && segment[i+2]=='(')){
                    size_t j=i+2;
                    int pdepth=1;
                    while(j<n && pdepth>0){
                        if(segment[j]=='(') ++pdepth;
                        else if(segment[j]==')') --pdepth;
                        if(pdepth>0) ++j;
                    }
                    size_t j=cmdSubstClose(segment,i+2);
                    std::string inner=segment.substr(i+2,j-(i+2));
                    flushRaw();
                    dqResult+=Shell::instance().runCaptured(inner);
@@ -1084,46 +1124,11 @@ std::vector<fresh::ShellWord> fresh::tokenizeShellWords(const std::string &input
                // real ';'-respecting parse of its contents once this
                // whole span reaches it as one piece). Same depth/quote/
                // backtick-aware scan as extractSubshellBody() itself.
                int depth=1;
                ++i;
                while(i<n && depth>0){
                    char c=input[i];
                    if(c=='\'' || c=='"'){
                        char q=c;
                        ++i;
                        // Double-quoted: a backslash-escaped quote char
                        // (`\"`) is NOT this span's own closing quote --
                        // same recognized escape set as needsMoreInput()/
                        // tokenizeShellArgs() elsewhere in this file. Single
                        // quotes stay fully literal (no escapes at all,
                        // matching POSIX): the `else` scan below handles
                        // that case unchanged.
                        if(q=='"'){
                            while(i<n && input[i]!=q){
                                if(input[i]=='\\' && i+1<n &&
                                   (input[i+1]=='"' || input[i+1]=='\\' ||
                                    input[i+1]=='$' || input[i+1]=='`'))
                                    i+=2;
                                else
                                    ++i;
                            }
                        }else{
                            while(i<n && input[i]!=q)
                                ++i;
                        }
                    }else if(c=='`'){
                        ++i;
                        while(i<n && input[i]!='`'){
                            if(input[i]=='\\' && i+1<n && input[i+1]=='`')
                                ++i;
                            ++i;
                        }
                    }else if(c=='('){
                        ++depth;
                    }else if(c==')'){
                        --depth;
                    }
                    ++i;
                // (cmdSubstClose(): quotes, escapes, backticks, nested
                // "$(" and case patterns' lone ')' are all skipped)
                {
                    size_t close=cmdSubstClose(input,i+1);
                    i=(close<n) ? close+1 : n;
                }
                continue;
            }
@@ -2003,13 +2008,8 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment,
        // that one specifically needs its own branch) silently split into
        // two broken pipeline stages instead of one command.
        if(c=='$' && i+1<n && segment[i+1]=='('){
            size_t j=i+2;
            int depth=1;
            while(j<n && depth>0){
                if(segment[j]=='(') ++depth;
                else if(segment[j]==')') --depth;
                ++j;
            }
            size_t j=cmdSubstClose(segment,i+2);
            if(j<n) ++j;
            current+=segment.substr(i,j-i);
            i=j;
            continue;
@@ -2169,53 +2169,12 @@ bool fresh::extractSubshellBody(const std::string &stage,std::string &body,std::
    if(i>=n || stage[i]!='(')
        return false;

    // Depth-tracked scan for the matching ')', skipping over quoted and
    // backtick-quoted spans so a '(' or ')' inside one of those (e.g. an
    // embedded "echo ')'") doesn't desync the count -- same approach as
    // splitPipelineStages()'s "$(...)" handling just above.
    size_t j=i+1;
    int depth=1;
    while(j<n && depth>0){
        char c=stage[j];
        if(c=='\'' || c=='"'){
            char q=c;
            ++j;
            // Double-quoted: a backslash-escaped quote (`\"`) is NOT this
            // span's own closing quote -- same escape-aware fix already
            // applied to tokenizeShellWords()'s own "(...)" absorption scan
            // (see its comment); without it, a body like
            // `( sed "s/\"//g" )` desyncs this depth count on the escaped
            // quote and returns a truncated/wrong body. Single quotes stay
            // fully literal (POSIX has no escapes inside them at all).
            if(q=='"'){
                while(j<n && stage[j]!=q){
                    if(stage[j]=='\\' && j+1<n &&
                       (stage[j+1]=='"' || stage[j+1]=='\\' ||
                        stage[j+1]=='$' || stage[j+1]=='`'))
                        j+=2;
                    else
                        ++j;
                }
            }else{
                while(j<n && stage[j]!=q)
                    ++j;
            }
        }else if(c=='`'){
            ++j;
            while(j<n && stage[j]!='`'){
                if(stage[j]=='\\' && j+1<n && stage[j+1]=='`')
                    ++j;
                ++j;
            }
        }else if(c=='('){
            ++depth;
        }else if(c==')'){
            --depth;
        }
        ++j;
    }
    if(depth>0)
    // the matching ')' (quotes, escapes, backticks, nested "$(" and case
    // patterns skipped -- see cmdSubstClose())
    size_t close=cmdSubstClose(stage,i+1);
    if(close>=n)
        return false; // unterminated "(" -- treat as not a subshell at all
    size_t j=close+1;

    body=stage.substr(i+1,(j-1)-(i+1));
    suffix=stage.substr(j);