Loading src/shell.cpp +98 −3 Changes for src/shell.cpp: 98 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -1829,6 +1829,46 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, // '|' check already respects. int braceDepth=0; // Depth of unclosed if/for/while/case keyword blocks -- e.g. the // kernel's own scripts/setlocalversion: "if { git ... || git ...; } | // read dummy; then ...; fi". By the time that '|' is reached, the // if-CONDITION's own "{...}" has already closed (braceDepth is back to // 0), but the enclosing "if...fi" is still very much open -- that pipe // is still part of building the if's own condition command, never a // boundary between two stages of some OUTER pipeline `runSimple()` // itself was asked to split. Without this, that '|' got treated as a // real top-level split point, breaking the whole "if...fi" into a // bogus multi-stage "pipeline" (stage 0 = "if { ... }", stage 1 = // "read dummy; then ...; fi") -- so the "if" builtin never even ran; // "then"/"fi" surfaced later as their own unrelated "command not // found" statements instead. Word-based (unlike braceDepth just // above, which is a plain character count): "if"/"elif"/"while"/ // "for"/"case" only count as real openers, and "fi"/"done"/"esac" // only as real closers, in actual command-name position -- the same // boundary set (start of text, or right after ';'/'\n'/'|'/"&&"/ // "||") parse()'s own per-statement scan and needsMoreInput() gate // the identical recognition on (see their own comments) -- so e.g. // "echo done" 's own "done" (cat's argument, not a real loop // terminator) can never desync this count. int keywordDepth=0; static const char *kwOpeners[]={"if","elif","while","for","case"}; static const char *kwClosers[]={"fi","done","esac"}; auto atKeywordStmtStart=[&](size_t pos)->bool{ size_t p=pos; while(p>0 && (segment[p-1]==' ' || segment[p-1]=='\t')) --p; if(p==0) return true; char c=segment[p-1]; if(c==';' || c=='\n' || c=='|') return true; if(c=='&' && p>=2 && segment[p-2]=='&') return true; return false; }; auto isWordBoundary=[&](size_t pos)->bool{ return pos>=segment.size() || segment[pos]==' ' || segment[pos]=='\t' || segment[pos]=='\n' || segment[pos]=='\r' || segment[pos]==';' || segment[pos]=='|' || segment[pos]=='&'; }; auto flushStage=[&](){ stages.push_back(current); current.clear(); Loading Loading @@ -2025,6 +2065,34 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, continue; } if(atKeywordStmtStart(i)){ bool matchedKw=false; for(const char *kw: kwOpeners){ size_t klen=strlen(kw); if(segment.compare(i,klen,kw)==0 && isWordBoundary(i+klen)){ ++keywordDepth; current+=segment.substr(i,klen); i+=klen; matchedKw=true; break; } } if(!matchedKw && keywordDepth>0){ for(const char *kw: kwClosers){ size_t klen=strlen(kw); if(segment.compare(i,klen,kw)==0 && isWordBoundary(i+klen)){ --keywordDepth; current+=segment.substr(i,klen); i+=klen; matchedKw=true; break; } } } if(matchedKw) continue; } if(c=='{'){ ++braceDepth; current+=c; Loading @@ -2038,7 +2106,7 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, continue; } if(c=='|' && braceDepth==0){ if(c=='|' && braceDepth==0 && keywordDepth==0){ flushStage(); ++i; continue; Loading Loading @@ -3230,9 +3298,25 @@ int fresh::Shell::runSegmentWords(const std::string &input,const std::vector<She // argument, not a real while-loop terminator) would desync // this depth count and either close the group too early or // (as observed) miss the real closing '}' entirely. // "if"/"elif"/"while" right before words[i] also opens a real // command position -- their own CONDITION can itself be a // compound command, e.g. the kernel's own // scripts/setlocalversion: "if { git ... || git ...; } | read // dummy; then ...; fi" as a function's own body. Without this, // that inner "{" was never counted as an opener at all (only // ';'/'&&'/'||'/'|' and the position right after THIS group's // own opening brace were), yet its own matching "}" (correctly // ';'-preceded) still closed -- decrementing `depth` back to 0 // and making this scan think the WHOLE group ended right there, // one "}" (and the entire rest of the real body: "| read // dummy; then ...; fi; ...") too early. Same root cause, and // same fix, as parse()'s own per-statement scan needed for the // identical reason (see its own comment). 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" || words[i-1].text=="elif" || words[i-1].text=="while"); if(atStmtStart && words[i].text=="{") ++depth; else if(atStmtStart && words[i].text=="}"){ if(--depth==0){ closeIdx=i; break; } Loading Loading @@ -3370,9 +3454,20 @@ void fresh::Shell::collectFunctionDefinitions(const std::string &input,std::vect for(size_t i=0;i<words.size();){ const std::string &w=words[i].text; // "if"/"elif"/"while" right before words[i] also opens a real // command position (their own CONDITION can itself be a compound // command, e.g. "if { ...; } | read x; then") -- same fix, same // reason, as this function's own depth counter just below needs it // for (see that comment): without it, a nested "{" used as an // if/while's own condition was never counted as a real opener here // either, letting its matching "}" desync `depth` and mistake some // enclosing function's own header/body match at a wrong, // coincidentally-depth-0 position. 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" || words[i-1].text=="elif" || words[i-1].text=="while"; if(depth==0 && atStmtStart){ std::string name; Loading Loading
src/shell.cpp +98 −3 Changes for src/shell.cpp: 98 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -1829,6 +1829,46 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, // '|' check already respects. int braceDepth=0; // Depth of unclosed if/for/while/case keyword blocks -- e.g. the // kernel's own scripts/setlocalversion: "if { git ... || git ...; } | // read dummy; then ...; fi". By the time that '|' is reached, the // if-CONDITION's own "{...}" has already closed (braceDepth is back to // 0), but the enclosing "if...fi" is still very much open -- that pipe // is still part of building the if's own condition command, never a // boundary between two stages of some OUTER pipeline `runSimple()` // itself was asked to split. Without this, that '|' got treated as a // real top-level split point, breaking the whole "if...fi" into a // bogus multi-stage "pipeline" (stage 0 = "if { ... }", stage 1 = // "read dummy; then ...; fi") -- so the "if" builtin never even ran; // "then"/"fi" surfaced later as their own unrelated "command not // found" statements instead. Word-based (unlike braceDepth just // above, which is a plain character count): "if"/"elif"/"while"/ // "for"/"case" only count as real openers, and "fi"/"done"/"esac" // only as real closers, in actual command-name position -- the same // boundary set (start of text, or right after ';'/'\n'/'|'/"&&"/ // "||") parse()'s own per-statement scan and needsMoreInput() gate // the identical recognition on (see their own comments) -- so e.g. // "echo done" 's own "done" (cat's argument, not a real loop // terminator) can never desync this count. int keywordDepth=0; static const char *kwOpeners[]={"if","elif","while","for","case"}; static const char *kwClosers[]={"fi","done","esac"}; auto atKeywordStmtStart=[&](size_t pos)->bool{ size_t p=pos; while(p>0 && (segment[p-1]==' ' || segment[p-1]=='\t')) --p; if(p==0) return true; char c=segment[p-1]; if(c==';' || c=='\n' || c=='|') return true; if(c=='&' && p>=2 && segment[p-2]=='&') return true; return false; }; auto isWordBoundary=[&](size_t pos)->bool{ return pos>=segment.size() || segment[pos]==' ' || segment[pos]=='\t' || segment[pos]=='\n' || segment[pos]=='\r' || segment[pos]==';' || segment[pos]=='|' || segment[pos]=='&'; }; auto flushStage=[&](){ stages.push_back(current); current.clear(); Loading Loading @@ -2025,6 +2065,34 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, continue; } if(atKeywordStmtStart(i)){ bool matchedKw=false; for(const char *kw: kwOpeners){ size_t klen=strlen(kw); if(segment.compare(i,klen,kw)==0 && isWordBoundary(i+klen)){ ++keywordDepth; current+=segment.substr(i,klen); i+=klen; matchedKw=true; break; } } if(!matchedKw && keywordDepth>0){ for(const char *kw: kwClosers){ size_t klen=strlen(kw); if(segment.compare(i,klen,kw)==0 && isWordBoundary(i+klen)){ --keywordDepth; current+=segment.substr(i,klen); i+=klen; matchedKw=true; break; } } } if(matchedKw) continue; } if(c=='{'){ ++braceDepth; current+=c; Loading @@ -2038,7 +2106,7 @@ std::vector<std::string> fresh::splitPipelineStages(const std::string &segment, continue; } if(c=='|' && braceDepth==0){ if(c=='|' && braceDepth==0 && keywordDepth==0){ flushStage(); ++i; continue; Loading Loading @@ -3230,9 +3298,25 @@ int fresh::Shell::runSegmentWords(const std::string &input,const std::vector<She // argument, not a real while-loop terminator) would desync // this depth count and either close the group too early or // (as observed) miss the real closing '}' entirely. // "if"/"elif"/"while" right before words[i] also opens a real // command position -- their own CONDITION can itself be a // compound command, e.g. the kernel's own // scripts/setlocalversion: "if { git ... || git ...; } | read // dummy; then ...; fi" as a function's own body. Without this, // that inner "{" was never counted as an opener at all (only // ';'/'&&'/'||'/'|' and the position right after THIS group's // own opening brace were), yet its own matching "}" (correctly // ';'-preceded) still closed -- decrementing `depth` back to 0 // and making this scan think the WHOLE group ended right there, // one "}" (and the entire rest of the real body: "| read // dummy; then ...; fi; ...") too early. Same root cause, and // same fix, as parse()'s own per-statement scan needed for the // identical reason (see its own comment). 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" || words[i-1].text=="elif" || words[i-1].text=="while"); if(atStmtStart && words[i].text=="{") ++depth; else if(atStmtStart && words[i].text=="}"){ if(--depth==0){ closeIdx=i; break; } Loading Loading @@ -3370,9 +3454,20 @@ void fresh::Shell::collectFunctionDefinitions(const std::string &input,std::vect for(size_t i=0;i<words.size();){ const std::string &w=words[i].text; // "if"/"elif"/"while" right before words[i] also opens a real // command position (their own CONDITION can itself be a compound // command, e.g. "if { ...; } | read x; then") -- same fix, same // reason, as this function's own depth counter just below needs it // for (see that comment): without it, a nested "{" used as an // if/while's own condition was never counted as a real opener here // either, letting its matching "}" desync `depth` and mistake some // enclosing function's own header/body match at a wrong, // coincidentally-depth-0 position. 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" || words[i-1].text=="elif" || words[i-1].text=="while"; if(depth==0 && atStmtStart){ std::string name; Loading