Commit 5889b75a authored by jan.koester's avatar jan.koester
Browse files

test

parent 3e92ec60
Loading
Loading
Loading
Loading
+73 −9
Changes for src/shell.cpp: 73 added lines, 9 removed lines.
Original line number Diff line number Diff line
@@ -2056,6 +2056,29 @@ bool fresh::needsMoreInput(const std::string &text){
               text[pos]=='\n' || text[pos]=='\r' || text[pos]==';';
    };

    // Whitespace alone (what isBoundary() above checks) only tells us a
    // candidate keyword isn't glued onto some longer identifier -- it does
    // NOT tell us the word is actually in command-name position. "echo
    // done" has plain word-separating whitespace right before "done" too,
    // but that "done" is cat's own argument, not a real while-loop
    // terminator; matching it as one anyway desyncs `depth` and makes this
    // function wrongly report a still-open "{"/if/for/while/case as
    // already-complete. Real command-name position is: the very start of
    // `text`, or right after skipping back over any plain whitespace to a
    // ';', a real newline, '|', or "&&"/"||" -- the same set of separators
    // parse()'s own per-statement scan and collectFunctionDefinitions()
    // gate the identical keyword recognition on (see their own comments).
    auto isStmtStart=[&](size_t pos)->bool{
        size_t p=pos;
        while(p>0 && (text[p-1]==' ' || text[p-1]=='\t'))
            --p;
        if(p==0) return true;
        char c=text[p-1];
        if(c==';' || c=='\n' || c=='|') return true;
        if(c=='&' && p>=2 && text[p-2]=='&') return true;
        return false;
    };

    while(i<n){
        if(inHeredoc){
            size_t lineStart=i;
@@ -2147,11 +2170,32 @@ bool fresh::needsMoreInput(const std::string &text){
        // tokenizeShellWords() naturally gluing the whole "${...}" into
        // one word the original word-based approach relied on.
        bool leftBoundary=(i==0) || isBoundary(i-1) || text[i-1]=='#';
        if(leftBoundary){
        bool stmtStart=isStmtStart(i);
        // A function definition's own "NAME()"/"NAME ()" header (glued or
        // spaced, tokenizeShellWords() accepts both) is the single most
        // common position for a "{" to open its body -- e.g. this exact
        // function's own real-world trigger, the Linux kernel's own
        // scripts/syscallhdr.sh: "usage() {". isStmtStart() alone rejects
        // it (nothing in its own recognized separator set includes a bare
        // ')'), so check for it separately, and ONLY for "{" specifically
        // -- unlike a real statement separator, a bare ')' right before
        // "done"/"fi"/"esac"/"}" (a command-substitution result used as a
        // plain argument, e.g. "echo $(date) done") is NOT a safe signal
        // that "done" here is a real keyword, so this must never relax
        // the closers' own, stricter check below.
        bool afterFuncHeaderParen=false;
        if(leftBoundary && !stmtStart){
            size_t p=i;
            while(p>0 && (text[p-1]==' ' || text[p-1]=='\t'))
                --p;
            afterFuncHeaderParen=(p>0 && text[p-1]==')');
        }
        if(leftBoundary && (stmtStart || afterFuncHeaderParen)){
            bool handled=false;
            for(const char *kw: openers){
                size_t klen=strlen(kw);
                if(text.compare(i,klen,kw)==0 && isBoundary(i+klen)){
                bool positionOk=stmtStart || (afterFuncHeaderParen && kw==std::string("{"));
                if(positionOk && text.compare(i,klen,kw)==0 && isBoundary(i+klen)){
                    ++depth;
                    i+=klen;
                    handled=true;
@@ -2159,6 +2203,7 @@ bool fresh::needsMoreInput(const std::string &text){
                }
            }
            if(handled) continue;
            if(stmtStart){
                for(const char *kw: closers){
                    size_t klen=strlen(kw);
                    if(text.compare(i,klen,kw)==0 && isBoundary(i+klen)){
@@ -2170,6 +2215,7 @@ bool fresh::needsMoreInput(const std::string &text){
                }
                if(handled) continue;
            }
        }

        ++i;
    }
@@ -2904,7 +2950,8 @@ int fresh::Shell::runSegmentWords(const std::string &input,const std::vector<She
            // this depth count and either close the group too early or
            // (as observed) miss the real closing '}' entirely.
            bool atStmtStart=(i==from+1 || words[i-1].text==";" ||
                               words[i-1].text=="&&" || words[i-1].text=="||");
                               words[i-1].text=="&&" || words[i-1].text=="||" ||
                               words[i-1].text=="|");
            if(atStmtStart && words[i].text=="{") ++depth;
            else if(atStmtStart && words[i].text=="}"){
                if(--depth==0){ closeIdx=i; break; }
@@ -3043,7 +3090,8 @@ void fresh::Shell::collectFunctionDefinitions(const std::string &input,std::vect
        const std::string &w=words[i].text;

        bool atStmtStart=(i==0) || words[i-1].text==";" ||
                          words[i-1].text=="&&" || words[i-1].text=="||";
                          words[i-1].text=="&&" || words[i-1].text=="||" ||
                          words[i-1].text=="|";

        if(depth==0 && atStmtStart){
            std::string name;
@@ -3148,9 +3196,16 @@ void fresh::Shell::collectFunctionDefinitions(const std::string &input,std::vect
            }
        }

        if(w=="if" || w=="for" || w=="while" || w=="case" || w=="{")
        // Same "must be in command-name position" guard as the outer
        // per-statement scan in parse() applies for the identical reason
        // (see its own comment) -- otherwise an ordinary argument word
        // that happens to equal one of these (a "done"/"fi"/"esac"/"}"
        // arriving via echo, a variable's value, etc.) desyncs this depth
        // count, throwing off whether a LATER real function header
        // anywhere else in this same input is seen at depth 0 or not.
        if(atStmtStart && (w=="if" || w=="for" || w=="while" || w=="case" || w=="{"))
            ++depth;
        else if(depth>0 && (w=="fi" || w=="done" || w=="esac" || w=="}"))
        else if(atStmtStart && depth>0 && (w=="fi" || w=="done" || w=="esac" || w=="}"))
            --depth;

        ++i;
@@ -3331,11 +3386,20 @@ int fresh::Shell::parse(const std::string &input,bool applyErrexit){
        // covers this SPECIFIC loop's own equivalent of "i==0" there (the
        // start of whichever ';'/'&&'/'||'-delimited segment is currently
        // being scanned, not necessarily the whole input's own word 0),
        // and the "!" case covers "! if ...; then ...; fi"'s negated
        // compound command, whose "if" sits right after "!", not right
        // after a real separator.
        // the "!" case covers "! if ...; then ...; fi"'s negated compound
        // command, whose "if" sits right after "!", not right after a real
        // separator, and "|" covers a compound command starting (or, for
        // a lone "}", nonsensically but harmlessly, ending) any stage of a
        // pipeline, not just its first one -- e.g. "echo hi | { echo abc |
        // tr a-z A-Z; echo done; } > file" (the Linux kernel's own
        // scripts/syscallhdr.sh uses exactly this shape): without this,
        // that "{" sat right after "|", was never recognized as an opener
        // at all, and every ';'/'&&'/'||' inside the group -- starting
        // with the very first one -- was wrongly treated as a top-level
        // segment boundary instead.
        bool atStmtStart=(i==segstart || words[i-1].text==";" ||
                           words[i-1].text=="&&" || words[i-1].text=="||" ||
                           words[i-1].text=="|" ||
                           (i==segstart+1 && words[segstart].text=="!"));
        if(atStmtStart && (w=="if" || w=="for" || w=="while" || w=="case" || w=="{"))
            ++depth;