Loading commands/shell/CMakeLists.txt +1 −0 Changes for commands/shell/CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ target_sources(fresh PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/echo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/printf.cpp ${CMAKE_CURRENT_SOURCE_DIR}/export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local.cpp ${CMAKE_CURRENT_SOURCE_DIR}/variables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/unset.cpp ${CMAKE_CURRENT_SOURCE_DIR}/read.cpp Loading commands/shell/local.cpp 0 → 100644 +69 −0 Changes for commands/shell/local.cpp: 69 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2022, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <string> #include <iostream> #include "shell.h" #include "envoirment.h" namespace fresh { namespace cmd { // local [-r|-x|...] NAME[=VALUE]... -- variables scoped to the running // function call (Shell::declareLocal()). Without a value the variable // starts out unset, as in bash and dash. class LocalCommand : public ShellCommand { public: LocalCommand():ShellCommand("local"){} int runCommand(int argc,const char **argv){ Shell &sh=Shell::instance(); Environment &env=Environment::instance(); int status=0; for(int i=1;i<argc;i++){ std::string arg=argv[i]; if(arg.size()>1 && arg[0]=='-' && arg.find('=')==std::string::npos) continue; // attribute flags (-r, -x, -i, ...): accepted, not tracked size_t eq=arg.find('='); std::string name=eq==std::string::npos ? arg : arg.substr(0,eq); if(!sh.declareLocal(name)){ std::cerr << "fresh: local: can only be used in a function" << std::endl; return 1; } if(eq!=std::string::npos) env.setVariable(name,arg.substr(eq+1)); else env.unsetVariable(name); } return status; } }; FRESH_REGISTER_COMMAND(LocalCommand); }} commands/shell/read.cpp +114 −57 Changes for commands/shell/read.cpp: 114 added lines, 57 removed lines. Original line number Diff line number Diff line Loading @@ -52,6 +52,10 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <unistd.h> #include <poll.h> #include <termios.h> #include <cstring> #include <cstdlib> #include "shell.h" #include "envoirment.h" Loading @@ -61,96 +65,149 @@ namespace fresh { namespace cmd { public: ReadCommand():ShellCommand("read"){} // read [-r] [-s] [-d DELIM] [-n N] [-p PROMPT] [-t SECS] [-u FD] [NAME...] // Fields are split at $IFS (default: space, tab, newline); "IFS= read" // keeps the line exactly, "-d ''" reads up to a NUL byte (the // "find -print0 | while read -r -d '' f" idiom). int runCommand(int argc,const char **argv){ bool raw=false; bool raw=false,silent=false; char delim='\n'; long maxChars=-1; double timeout=-1; int fd=0; std::string prompt; int argi=1; for(; argi<argc; ++argi){ std::string a=argv[argi]; if(a=="--"){ ++argi; break; } if(a=="-r"){ raw=true; continue; } if(!a.empty() && a[0]=='-' && a!="-"){ std::cerr << "fresh: read: " << a << ": unsupported option" << std::endl; if(a.size()<2 || a[0]!='-') break; bool stop=false; for(size_t k=1;k<a.size() && !stop;++k){ char c=a[k]; if(c=='r'){ raw=true; continue; } if(c=='s'){ silent=true; continue; } if(strchr("dnptu",c)){ std::string v=a.substr(k+1); if(v.empty() && k+1==a.size()){ if(argi+1>=argc){ std::cerr << "fresh: read: -" << c << ": option requires an argument" << std::endl; return 2; } v=argv[++argi]; } switch(c){ case 'd': delim=v.empty() ? '\0' : v[0]; break; case 'n': maxChars=atol(v.c_str()); break; case 'p': prompt=v; break; case 't': timeout=atof(v.c_str()); break; case 'u': fd=atoi(v.c_str()); break; } stop=true; continue; } std::cerr << "fresh: read: -" << c << ": invalid option" << std::endl; return 2; } break; } std::vector<std::string> names; for(; argi<argc; ++argi) names.push_back(argv[argi]); if(names.empty()) bool reply=names.empty(); if(reply) names.push_back("REPLY"); // POSIX: "\<newline>" is a line continuation removed entirely // (both characters dropped, the logical line keeps going); any // other "\X" has just the backslash removed, keeping X literal // -- unless "-r" was given, in which case backslash is fully // ordinary input. A trailing, unpaired backslash right at EOF // is kept as-is, matching bash. if(!prompt.empty() && isatty(fd)){ std::cerr << prompt; std::cerr.flush(); } struct termios saved; bool restoreTerm=false; if(silent && isatty(fd) && tcgetattr(fd,&saved)==0){ struct termios t=saved; t.c_lflag&=~(ECHO); tcsetattr(fd,TCSANOW,&t); restoreTerm=true; } // POSIX: "\<newline>" is a line continuation removed entirely; // any other "\X" keeps X literal -- unless -r was given, in // which case backslash is ordinary input. std::string line; bool sawEOF=false; bool sawEOF=false,timedOut=false; long count=0; auto getc=[&](char &c)->int{ if(timeout>=0){ struct pollfd p; p.fd=fd; p.events=POLLIN; p.revents=0; int r; do{ r=poll(&p,1,(int)(timeout*1000)); }while(r<0 && errno==EINTR); if(r==0){ timedOut=true; return 0; } } ssize_t n; do{ n=::read(fd,&c,1); }while(n<0 && errno==EINTR); return n>0 ? 1 : 0; }; for(;;){ if(maxChars>=0 && count>=maxChars) break; char c; ssize_t n; do{ n=::read(0,&c,1); }while(n<0 && errno==EINTR); if(n<=0){ sawEOF=true; break; } if(c=='\n') break; if(!getc(c)){ sawEOF=true; break; } if(c==delim) break; if(!raw && c=='\\'){ char next; ssize_t n2; do{ n2=::read(0,&next,1); }while(n2<0 && errno==EINTR); if(n2<=0){ line+=c; sawEOF=true; break; } if(next=='\n') continue; if(!getc(next)){ line+=c; sawEOF=true; break; } if(next=='\n') continue; line+=next; ++count; continue; } line+=c; ++count; } if(restoreTerm){ tcsetattr(fd,TCSANOW,&saved); std::cerr << std::endl; } // Split on plain whitespace -- fresh has no real $IFS variable // anywhere else either (word-splitting elsewhere in this shell // is hardcoded to space/tab/newline too), so this matches the // rest of the shell rather than adding IFS support read alone // would need. The last name gets whatever remains of the line // (internal spacing preserved, only its own trailing // whitespace trimmed) -- standard read behavior for // "read a b" against more than two fields. Environment &env=Environment::instance(); std::string ifs=" \t\n"; bool ifsSet=env.getVariable("IFS",ifs); if(!ifsSet) ifs=" \t\n"; if(reply){ // plain "read": REPLY gets the line as is env.setVariable("REPLY",line); }else if(ifs.empty()){ // IFS empty: no splitting, no trimming env.setVariable(names[0],line); for(size_t k=1;k<names.size();++k) env.setVariable(names[k],""); }else{ auto isIfs=[&](char c){ return ifs.find(c)!=std::string::npos; }; auto isIfsWs=[&](char c){ return (c==' ' || c=='\t' || c=='\n') && isIfs(c); }; size_t i=0,len=line.size(); while(i<len && (line[i]==' ' || line[i]=='\t')) ++i; while(i<len && isIfsWs(line[i])) ++i; for(size_t k=0;k<names.size();++k){ bool last=(k+1==names.size()); if(last){ if(k+1==names.size()){ // the last name gets the rest, trailing IFS whitespace trimmed size_t end=len; while(end>i && (line[end-1]==' ' || line[end-1]=='\t')) --end; Environment::instance().setVariable(names[k],line.substr(i,end-i)); while(end>i && isIfsWs(line[end-1])) --end; env.setVariable(names[k],line.substr(i,end-i)); break; } size_t start=i; while(i<len && line[i]!=' ' && line[i]!='\t') ++i; Environment::instance().setVariable(names[k],line.substr(start,i-start)); while(i<len && (line[i]==' ' || line[i]=='\t')) while(i<len && !isIfs(line[i])) ++i; env.setVariable(names[k],line.substr(start,i-start)); // one non-whitespace separator (plus surrounding whitespace) while(i<len && isIfsWs(line[i])) ++i; if(i<len && isIfs(line[i]) && !isIfsWs(line[i])){ ++i; while(i<len && isIfsWs(line[i])) ++i; } } } if(timedOut) return 142; // like bash: EOF before the delimiter is a failure (the variables // still get what was read) return sawEOF ? 1 : 0; } }; Loading src/shell.cpp +28 −0 Changes for src/shell.cpp: 28 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1462,7 +1462,19 @@ int fresh::Shell::callFunction(const std::string &name,const std::vector<std::st _loopDepth=0; ++_functionDepth; _localFrames.emplace_back(); int status=parse(it->second); // undo this call's "local"s, newest first std::vector<LocalSave> frame=std::move(_localFrames.back()); _localFrames.pop_back(); for(auto l=frame.rbegin(); l!=frame.rend(); ++l){ if(l->wasSet){ env.setVariable(l->name,l->value); if(l->wasExported) env.exportVariable(l->name); }else{ env.unsetVariable(l->name); } } --_functionDepth; _loopDepth=savedLoopDepth; Loading @@ -1479,6 +1491,22 @@ int fresh::Shell::callFunction(const std::string &name,const std::vector<std::st return status; } bool fresh::Shell::declareLocal(const std::string &name){ if(_localFrames.empty()) return false; std::vector<LocalSave> &frame=_localFrames.back(); for(auto &l: frame) if(l.name==name) return true; // already local in this call Environment &env=Environment::instance(); LocalSave save; save.name=name; save.wasSet=env.getVariable(name,save.value); save.wasExported=save.wasSet && env.isExported(name); frame.push_back(save); return true; } fresh::Shell::~Shell(){ } Loading src/shell.h +13 −0 Changes for src/shell.h: 13 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -482,6 +482,11 @@ namespace fresh { // via "return" -- this is the one place that ever does so. int callFunction(const std::string &name,const std::vector<std::string> &args); // "local NAME[=VALUE]": makes NAME local to the innermost running // function call -- its previous state (value, set or not, exported) // is restored when that call returns. False outside any function. bool declareLocal(const std::string &name); // Top-level input lines only (not sub-fragments run recursively by // if/for/while), recorded by main()'s REPL/script loop so the // "history" builtin has something to show. Loading Loading @@ -644,6 +649,14 @@ namespace fresh { // name -> raw compound-command source text, see // collectFunctionDefinitions()'s own comment. std::map<std::string,std::string> _functions; struct LocalSave { std::string name; bool wasSet; std::string value; bool wasExported; }; // one frame per running callFunction(), innermost last std::vector<std::vector<LocalSave>> _localFrames; bool _returning; int _returnCode; // Depth of nested callFunction() calls currently on the (real, Loading Loading
commands/shell/CMakeLists.txt +1 −0 Changes for commands/shell/CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ target_sources(fresh PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/echo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/printf.cpp ${CMAKE_CURRENT_SOURCE_DIR}/export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local.cpp ${CMAKE_CURRENT_SOURCE_DIR}/variables.cpp ${CMAKE_CURRENT_SOURCE_DIR}/unset.cpp ${CMAKE_CURRENT_SOURCE_DIR}/read.cpp Loading
commands/shell/local.cpp 0 → 100644 +69 −0 Changes for commands/shell/local.cpp: 69 added lines, 0 removed lines. Original line number Diff line number Diff line /******************************************************************************* Copyright (c) 2022, Jan Koester jan.koester@gmx.net All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the <organization> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *******************************************************************************/ #include <string> #include <iostream> #include "shell.h" #include "envoirment.h" namespace fresh { namespace cmd { // local [-r|-x|...] NAME[=VALUE]... -- variables scoped to the running // function call (Shell::declareLocal()). Without a value the variable // starts out unset, as in bash and dash. class LocalCommand : public ShellCommand { public: LocalCommand():ShellCommand("local"){} int runCommand(int argc,const char **argv){ Shell &sh=Shell::instance(); Environment &env=Environment::instance(); int status=0; for(int i=1;i<argc;i++){ std::string arg=argv[i]; if(arg.size()>1 && arg[0]=='-' && arg.find('=')==std::string::npos) continue; // attribute flags (-r, -x, -i, ...): accepted, not tracked size_t eq=arg.find('='); std::string name=eq==std::string::npos ? arg : arg.substr(0,eq); if(!sh.declareLocal(name)){ std::cerr << "fresh: local: can only be used in a function" << std::endl; return 1; } if(eq!=std::string::npos) env.setVariable(name,arg.substr(eq+1)); else env.unsetVariable(name); } return status; } }; FRESH_REGISTER_COMMAND(LocalCommand); }}
commands/shell/read.cpp +114 −57 Changes for commands/shell/read.cpp: 114 added lines, 57 removed lines. Original line number Diff line number Diff line Loading @@ -52,6 +52,10 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <unistd.h> #include <poll.h> #include <termios.h> #include <cstring> #include <cstdlib> #include "shell.h" #include "envoirment.h" Loading @@ -61,96 +65,149 @@ namespace fresh { namespace cmd { public: ReadCommand():ShellCommand("read"){} // read [-r] [-s] [-d DELIM] [-n N] [-p PROMPT] [-t SECS] [-u FD] [NAME...] // Fields are split at $IFS (default: space, tab, newline); "IFS= read" // keeps the line exactly, "-d ''" reads up to a NUL byte (the // "find -print0 | while read -r -d '' f" idiom). int runCommand(int argc,const char **argv){ bool raw=false; bool raw=false,silent=false; char delim='\n'; long maxChars=-1; double timeout=-1; int fd=0; std::string prompt; int argi=1; for(; argi<argc; ++argi){ std::string a=argv[argi]; if(a=="--"){ ++argi; break; } if(a=="-r"){ raw=true; continue; } if(!a.empty() && a[0]=='-' && a!="-"){ std::cerr << "fresh: read: " << a << ": unsupported option" << std::endl; if(a.size()<2 || a[0]!='-') break; bool stop=false; for(size_t k=1;k<a.size() && !stop;++k){ char c=a[k]; if(c=='r'){ raw=true; continue; } if(c=='s'){ silent=true; continue; } if(strchr("dnptu",c)){ std::string v=a.substr(k+1); if(v.empty() && k+1==a.size()){ if(argi+1>=argc){ std::cerr << "fresh: read: -" << c << ": option requires an argument" << std::endl; return 2; } v=argv[++argi]; } switch(c){ case 'd': delim=v.empty() ? '\0' : v[0]; break; case 'n': maxChars=atol(v.c_str()); break; case 'p': prompt=v; break; case 't': timeout=atof(v.c_str()); break; case 'u': fd=atoi(v.c_str()); break; } stop=true; continue; } std::cerr << "fresh: read: -" << c << ": invalid option" << std::endl; return 2; } break; } std::vector<std::string> names; for(; argi<argc; ++argi) names.push_back(argv[argi]); if(names.empty()) bool reply=names.empty(); if(reply) names.push_back("REPLY"); // POSIX: "\<newline>" is a line continuation removed entirely // (both characters dropped, the logical line keeps going); any // other "\X" has just the backslash removed, keeping X literal // -- unless "-r" was given, in which case backslash is fully // ordinary input. A trailing, unpaired backslash right at EOF // is kept as-is, matching bash. if(!prompt.empty() && isatty(fd)){ std::cerr << prompt; std::cerr.flush(); } struct termios saved; bool restoreTerm=false; if(silent && isatty(fd) && tcgetattr(fd,&saved)==0){ struct termios t=saved; t.c_lflag&=~(ECHO); tcsetattr(fd,TCSANOW,&t); restoreTerm=true; } // POSIX: "\<newline>" is a line continuation removed entirely; // any other "\X" keeps X literal -- unless -r was given, in // which case backslash is ordinary input. std::string line; bool sawEOF=false; bool sawEOF=false,timedOut=false; long count=0; auto getc=[&](char &c)->int{ if(timeout>=0){ struct pollfd p; p.fd=fd; p.events=POLLIN; p.revents=0; int r; do{ r=poll(&p,1,(int)(timeout*1000)); }while(r<0 && errno==EINTR); if(r==0){ timedOut=true; return 0; } } ssize_t n; do{ n=::read(fd,&c,1); }while(n<0 && errno==EINTR); return n>0 ? 1 : 0; }; for(;;){ if(maxChars>=0 && count>=maxChars) break; char c; ssize_t n; do{ n=::read(0,&c,1); }while(n<0 && errno==EINTR); if(n<=0){ sawEOF=true; break; } if(c=='\n') break; if(!getc(c)){ sawEOF=true; break; } if(c==delim) break; if(!raw && c=='\\'){ char next; ssize_t n2; do{ n2=::read(0,&next,1); }while(n2<0 && errno==EINTR); if(n2<=0){ line+=c; sawEOF=true; break; } if(next=='\n') continue; if(!getc(next)){ line+=c; sawEOF=true; break; } if(next=='\n') continue; line+=next; ++count; continue; } line+=c; ++count; } if(restoreTerm){ tcsetattr(fd,TCSANOW,&saved); std::cerr << std::endl; } // Split on plain whitespace -- fresh has no real $IFS variable // anywhere else either (word-splitting elsewhere in this shell // is hardcoded to space/tab/newline too), so this matches the // rest of the shell rather than adding IFS support read alone // would need. The last name gets whatever remains of the line // (internal spacing preserved, only its own trailing // whitespace trimmed) -- standard read behavior for // "read a b" against more than two fields. Environment &env=Environment::instance(); std::string ifs=" \t\n"; bool ifsSet=env.getVariable("IFS",ifs); if(!ifsSet) ifs=" \t\n"; if(reply){ // plain "read": REPLY gets the line as is env.setVariable("REPLY",line); }else if(ifs.empty()){ // IFS empty: no splitting, no trimming env.setVariable(names[0],line); for(size_t k=1;k<names.size();++k) env.setVariable(names[k],""); }else{ auto isIfs=[&](char c){ return ifs.find(c)!=std::string::npos; }; auto isIfsWs=[&](char c){ return (c==' ' || c=='\t' || c=='\n') && isIfs(c); }; size_t i=0,len=line.size(); while(i<len && (line[i]==' ' || line[i]=='\t')) ++i; while(i<len && isIfsWs(line[i])) ++i; for(size_t k=0;k<names.size();++k){ bool last=(k+1==names.size()); if(last){ if(k+1==names.size()){ // the last name gets the rest, trailing IFS whitespace trimmed size_t end=len; while(end>i && (line[end-1]==' ' || line[end-1]=='\t')) --end; Environment::instance().setVariable(names[k],line.substr(i,end-i)); while(end>i && isIfsWs(line[end-1])) --end; env.setVariable(names[k],line.substr(i,end-i)); break; } size_t start=i; while(i<len && line[i]!=' ' && line[i]!='\t') ++i; Environment::instance().setVariable(names[k],line.substr(start,i-start)); while(i<len && (line[i]==' ' || line[i]=='\t')) while(i<len && !isIfs(line[i])) ++i; env.setVariable(names[k],line.substr(start,i-start)); // one non-whitespace separator (plus surrounding whitespace) while(i<len && isIfsWs(line[i])) ++i; if(i<len && isIfs(line[i]) && !isIfsWs(line[i])){ ++i; while(i<len && isIfsWs(line[i])) ++i; } } } if(timedOut) return 142; // like bash: EOF before the delimiter is a failure (the variables // still get what was read) return sawEOF ? 1 : 0; } }; Loading
src/shell.cpp +28 −0 Changes for src/shell.cpp: 28 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -1462,7 +1462,19 @@ int fresh::Shell::callFunction(const std::string &name,const std::vector<std::st _loopDepth=0; ++_functionDepth; _localFrames.emplace_back(); int status=parse(it->second); // undo this call's "local"s, newest first std::vector<LocalSave> frame=std::move(_localFrames.back()); _localFrames.pop_back(); for(auto l=frame.rbegin(); l!=frame.rend(); ++l){ if(l->wasSet){ env.setVariable(l->name,l->value); if(l->wasExported) env.exportVariable(l->name); }else{ env.unsetVariable(l->name); } } --_functionDepth; _loopDepth=savedLoopDepth; Loading @@ -1479,6 +1491,22 @@ int fresh::Shell::callFunction(const std::string &name,const std::vector<std::st return status; } bool fresh::Shell::declareLocal(const std::string &name){ if(_localFrames.empty()) return false; std::vector<LocalSave> &frame=_localFrames.back(); for(auto &l: frame) if(l.name==name) return true; // already local in this call Environment &env=Environment::instance(); LocalSave save; save.name=name; save.wasSet=env.getVariable(name,save.value); save.wasExported=save.wasSet && env.isExported(name); frame.push_back(save); return true; } fresh::Shell::~Shell(){ } Loading
src/shell.h +13 −0 Changes for src/shell.h: 13 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -482,6 +482,11 @@ namespace fresh { // via "return" -- this is the one place that ever does so. int callFunction(const std::string &name,const std::vector<std::string> &args); // "local NAME[=VALUE]": makes NAME local to the innermost running // function call -- its previous state (value, set or not, exported) // is restored when that call returns. False outside any function. bool declareLocal(const std::string &name); // Top-level input lines only (not sub-fragments run recursively by // if/for/while), recorded by main()'s REPL/script loop so the // "history" builtin has something to show. Loading Loading @@ -644,6 +649,14 @@ namespace fresh { // name -> raw compound-command source text, see // collectFunctionDefinitions()'s own comment. std::map<std::string,std::string> _functions; struct LocalSave { std::string name; bool wasSet; std::string value; bool wasExported; }; // one frame per running callFunction(), innermost last std::vector<std::vector<LocalSave>> _localFrames; bool _returning; int _returnCode; // Depth of nested callFunction() calls currently on the (real, Loading