Loading src/shell.cpp +79 −0 Changes for src/shell.cpp: 79 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -732,6 +732,85 @@ std::vector<std::string> fresh::tokenizeShellArgs(const std::string &segment){ } }; while(i<n && segment[i]!='"'){ // "${...}" parameter expansion inside double quotes -- same // fix as the top-level scan above (see its own comment for // the full "$(...) nested inside an operator's default // word" story): scanned here as one complete, brace-depth- // matched unit, with any nested "$(...)"/"`...`" resolved // via runCaptured() as the scan proceeds (honoring this // context's own double-quote escape rules -- \$, \`, \", \\ // -- exactly like the plain-character accumulation just // below already does), BEFORE the standalone "$(cmd)"/ // "`cmd`" branches just below ever get a chance to // intercept a nested substitution mid-scan and flushRaw() a // syntactically incomplete "${VAR:-" fragment on its own. if(segment[i]=='$' && i+1<n && segment[i+1]=='{'){ size_t j=i+2; int depth=1; std::string rawExpr; while(j<n && depth>0){ char cj=segment[j]; if(cj=='{'){ ++depth; rawExpr+=cj; ++j; }else if(cj=='}'){ --depth; if(depth>0) rawExpr+=cj; ++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; } 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]=='`'){ rawExpr+='`'; j+=2; }else if(cj=='`'){ size_t k=j+1; std::string inner; while(k<n && segment[k]!='`'){ if(segment[k]=='\\' && k+1<n && (segment[k+1]=='`' || segment[k+1]=='$' || segment[k+1]=='"' || segment[k+1]=='\\')){ inner+=segment[k+1]; k+=2; continue; } inner+=segment[k]; ++k; } rawExpr+=Shell::instance().runCaptured(inner); j=(k<n) ? k+1 : k; }else if(cj=='\\' && j+1<n && segment[j+1]=='$'){ rawExpr+=kLiteralDollarMarker; j+=2; }else if(cj=='\\' && j+1<n && (segment[j+1]=='"' || segment[j+1]=='\\')){ rawExpr+=segment[j+1]; j+=2; }else{ rawExpr+=cj; ++j; } } if(depth==0){ flushRaw(); dqResult+=fresh::Environment::instance().expand("${"+rawExpr+"}"); i=j; continue; } // unterminated -- fall through, let '$' accumulate as // an ordinary character below (same "not full parity" // handling the top-level scan's own unterminated case // already gets). } // "$(cmd)" inside double quotes -- NOT "$((expr))" (excluded // the same way the top-level scan excludes it, leaving it to // fall through to the plain accumulation below, so Loading Loading
src/shell.cpp +79 −0 Changes for src/shell.cpp: 79 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -732,6 +732,85 @@ std::vector<std::string> fresh::tokenizeShellArgs(const std::string &segment){ } }; while(i<n && segment[i]!='"'){ // "${...}" parameter expansion inside double quotes -- same // fix as the top-level scan above (see its own comment for // the full "$(...) nested inside an operator's default // word" story): scanned here as one complete, brace-depth- // matched unit, with any nested "$(...)"/"`...`" resolved // via runCaptured() as the scan proceeds (honoring this // context's own double-quote escape rules -- \$, \`, \", \\ // -- exactly like the plain-character accumulation just // below already does), BEFORE the standalone "$(cmd)"/ // "`cmd`" branches just below ever get a chance to // intercept a nested substitution mid-scan and flushRaw() a // syntactically incomplete "${VAR:-" fragment on its own. if(segment[i]=='$' && i+1<n && segment[i+1]=='{'){ size_t j=i+2; int depth=1; std::string rawExpr; while(j<n && depth>0){ char cj=segment[j]; if(cj=='{'){ ++depth; rawExpr+=cj; ++j; }else if(cj=='}'){ --depth; if(depth>0) rawExpr+=cj; ++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; } 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]=='`'){ rawExpr+='`'; j+=2; }else if(cj=='`'){ size_t k=j+1; std::string inner; while(k<n && segment[k]!='`'){ if(segment[k]=='\\' && k+1<n && (segment[k+1]=='`' || segment[k+1]=='$' || segment[k+1]=='"' || segment[k+1]=='\\')){ inner+=segment[k+1]; k+=2; continue; } inner+=segment[k]; ++k; } rawExpr+=Shell::instance().runCaptured(inner); j=(k<n) ? k+1 : k; }else if(cj=='\\' && j+1<n && segment[j+1]=='$'){ rawExpr+=kLiteralDollarMarker; j+=2; }else if(cj=='\\' && j+1<n && (segment[j+1]=='"' || segment[j+1]=='\\')){ rawExpr+=segment[j+1]; j+=2; }else{ rawExpr+=cj; ++j; } } if(depth==0){ flushRaw(); dqResult+=fresh::Environment::instance().expand("${"+rawExpr+"}"); i=j; continue; } // unterminated -- fall through, let '$' accumulate as // an ordinary character below (same "not full parity" // handling the top-level scan's own unterminated case // already gets). } // "$(cmd)" inside double quotes -- NOT "$((expr))" (excluded // the same way the top-level scan excludes it, leaving it to // fall through to the plain accumulation below, so Loading