From e33bd6bd784ba0334beb0481d82416be38fbb54d Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Fri, 17 Jan 2014 15:56:32 -0600 Subject: NSH: Add support for while-do-done and until-do-done loops --- apps/ChangeLog.txt | 4 + apps/nshlib/README.txt | 17 ++ apps/nshlib/nsh.h | 61 +++++-- apps/nshlib/nsh_command.c | 14 +- apps/nshlib/nsh_parse.c | 364 +++++++++++++++++++++++++++++++++---- apps/nshlib/nsh_script.c | 17 ++ nuttx/Documentation/NuttShell.html | 45 ++++- 7 files changed, 459 insertions(+), 63 deletions(-) diff --git a/apps/ChangeLog.txt b/apps/ChangeLog.txt index e34781464..dfb45293e 100644 --- a/apps/ChangeLog.txt +++ b/apps/ChangeLog.txt @@ -788,3 +788,7 @@ * apps/nshlib/nsh_script.c: Now saves the FILE stream for the script file in the vtbl structure so that it can be accessed by forthcoming while and until logic (2014-1-17). + * apps/nshlib/nsh.h, nsh_command.c, nsh_parse.c, and nsh_script.c: Add + support for while-do-done and until-do-done loops. These only work + when executing a script file because they depend on the ability to seek + in the file to implement the looping behaviors (2014-1-17). diff --git a/apps/nshlib/README.txt b/apps/nshlib/README.txt index 11d692dc1..1e4731066 100644 --- a/apps/nshlib/README.txt +++ b/apps/nshlib/README.txt @@ -104,6 +104,23 @@ Conditional Command Execution [sequence of ] fi +Looping +^^^^^^^ + + while-do-done and until-do-done looping constructs are also supported. + These works from the command line but are primarily intended for use + within NSH scripts (see the sh command). The syntax is as follows: + + while ; do ; done + + Execute as long as has an exit status of + zero. + + until ; do ; done + + Execute as long as has a non-zero exit + status. + Built-In Variables ^^^^^^^^^^^^^^^^^^ diff --git a/apps/nshlib/nsh.h b/apps/nshlib/nsh.h index a9db6e5d2..4786692f7 100644 --- a/apps/nshlib/nsh.h +++ b/apps/nshlib/nsh.h @@ -455,22 +455,42 @@ ****************************************************************************/ /* State when parsing and if-then-else sequence */ -enum nsh_parser_e +enum nsh_itef_e { - NSH_PARSER_NORMAL = 0, /* Not in any special sequence */ - NSH_PARSER_IF, /* Just parsed 'if', expect condition */ - NSH_PARSER_THEN, /* Just parsed 'then', looking for 'else' or 'fi' */ - NSH_PARSER_ELSE /* Just parsed 'else', look for 'fi' */ + NSH_ITEF_NORMAL = 0, /* Not in an if-then-else sequence */ + NSH_ITEF_IF, /* Just parsed 'if', expect condition */ + NSH_ITEF_THEN, /* Just parsed 'then', looking for 'else' or 'fi' */ + NSH_ITEF_ELSE /* Just parsed 'else', look for 'fi' */ }; /* All state data for parsing one if-then-else sequence */ -struct nsh_ifthenelse_s +struct nsh_itef_s { - uint8_t ie_ifcond : 1; /* Value of command in 'if' statement */ - uint8_t ie_disabled : 1; /* TRUE: Unconditionally disabled */ + uint8_t ie_ifcond : 1; /* Value of command in 'if' statement */ + uint8_t ie_disabled : 1; /* TRUE: Unconditionally disabled */ uint8_t ie_unused : 4; - uint8_t ie_state : 2; /* Parser state (see enum nsh_parser_e) */ + uint8_t ie_state : 2; /* If-then-else state (see enum nsh_itef_e) */ +}; + +/* State when parsing and while-do-done or until-do-done sequence */ + +enum nsh_lp_e +{ + NSH_LOOP_NORMAL = 0, /* Not in a while-do-done or until-do-done sequence */ + NSH_LOOP_WHILE, /* Just parsed 'while', expect condition */ + NSH_LOOP_UNTIL, /* Just parsed 'until', expect condition */ + NSH_LOOP_DO /* Just parsed 'do', looking for 'done' */ +}; + +/* All state data for parsing one while-do-done or until-do-done sequence */ + +struct nsh_loop_s +{ + uint8_t lp_enable : 1; /* Loop command processing is enabled */ + uint8_t lp_unused : 5; + uint8_t lp_state : 2; /* Loop state (see enume nsh_lp_e) */ + long lp_topoffs; /* Top of loop file offset */ }; /* These structure provides the overall state of the parser */ @@ -478,23 +498,30 @@ struct nsh_ifthenelse_s struct nsh_parser_s { #ifndef CONFIG_NSH_DISABLEBG - bool np_bg; /* true: The last command executed in background */ + bool np_bg; /* true: The last command executed in background */ #endif #if CONFIG_NFILE_STREAMS > 0 - bool np_redirect; /* true: Output from the last command was re-directed */ + bool np_redirect; /* true: Output from the last command was re-directed */ #endif - bool np_fail; /* true: The last command failed */ + bool np_fail; /* true: The last command failed */ #ifndef CONFIG_NSH_DISABLEBG - int np_nice; /* "nice" value applied to last background cmd */ + int np_nice; /* "nice" value applied to last background cmd */ #endif #ifndef CONFIG_NSH_DISABLESCRIPT - FILE *np_stream; /* Stream of current script */ - uint8_t np_iendx; /* Current index into np_iestate[] */ + FILE *np_stream; /* Stream of current script */ + long np_foffs; /* File offset to the beginning of a line */ +#ifndef NSH_DISABLE_SEMICOLON + uint16_t np_loffs; /* Byte offset to the beginning of a command */ + bool np_jump; /* "Jump" to the top of the loop */ +#endif + uint8_t np_iendx; /* Current index into np_iestate[] */ + uint8_t np_lpndx; /* Current index into np_lpstate[] */ - /* This is a stack of if-then-else state information. */ + /* This is a stack of parser state information. */ - struct nsh_ifthenelse_s np_iestate[CONFIG_NSH_NESTDEPTH]; + struct nsh_itef_s np_iestate[CONFIG_NSH_NESTDEPTH]; + struct nsh_loop_s np_lpstate[CONFIG_NSH_NESTDEPTH]; #endif }; diff --git a/apps/nshlib/nsh_command.c b/apps/nshlib/nsh_command.c index 8321ad876..6ae3acfa7 100644 --- a/apps/nshlib/nsh_command.c +++ b/apps/nshlib/nsh_command.c @@ -460,9 +460,9 @@ static inline void help_usage(FAR struct nsh_vtbl_s *vtbl) { nsh_output(vtbl, "NSH command forms:\n"); #ifndef CONFIG_NSH_DISABLEBG - nsh_output(vtbl, " [nice [-d >]] [> |>> ] [&]\n"); + nsh_output(vtbl, " [nice [-d >]] [> |>> ] [&]\n\n"); #else - nsh_output(vtbl, " [> |>> ]\n"); + nsh_output(vtbl, " [> |>> ]\n\n"); #endif #ifndef CONFIG_NSH_DISABLESCRIPT nsh_output(vtbl, "OR\n"); @@ -472,6 +472,16 @@ static inline void help_usage(FAR struct nsh_vtbl_s *vtbl) nsh_output(vtbl, " else\n"); nsh_output(vtbl, " [sequence of ]\n"); nsh_output(vtbl, " fi\n\n"); + nsh_output(vtbl, "OR\n"); + nsh_output(vtbl, " while \n"); + nsh_output(vtbl, " do\n"); + nsh_output(vtbl, " [sequence of ]\n"); + nsh_output(vtbl, " done\n\n"); + nsh_output(vtbl, "OR\n"); + nsh_output(vtbl, " until \n"); + nsh_output(vtbl, " do\n"); + nsh_output(vtbl, " [sequence of ]\n"); + nsh_output(vtbl, " done\n\n"); #endif } #endif diff --git a/apps/nshlib/nsh_parse.c b/apps/nshlib/nsh_parse.c index a83b9e999..a612e892a 100644 --- a/apps/nshlib/nsh_parse.c +++ b/apps/nshlib/nsh_parse.c @@ -160,9 +160,13 @@ static FAR char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, char **saveptr, FAR NSH_MEMLIST_TYPE *memlist); #ifndef CONFIG_NSH_DISABLESCRIPT +static bool nsh_loop_enabled(FAR struct nsh_vtbl_s *vtbl); +static bool nsh_itef_enabled(FAR struct nsh_vtbl_s *vtbl); static bool nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl); -static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, - FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist); +static int nsh_loop(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, + FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist); +static int nsh_itef(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, + FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist); #endif #ifndef CONFIG_NSH_DISABLEBG @@ -392,7 +396,41 @@ static int nsh_saveresult(FAR struct nsh_vtbl_s *vtbl, bool result) struct nsh_parser_s *np = &vtbl->np; #ifndef CONFIG_NSH_DISABLESCRIPT - if (np->np_iestate[np->np_iendx].ie_state == NSH_PARSER_IF) + /* Check if we are waiting for the condition associated with a while + * token. + * + * while ; do ; done + * + * Execute as long as has an exit status of + * zero. + */ + + if (np->np_lpstate[np->np_lpndx].lp_state == NSH_LOOP_WHILE) + { + np->np_fail = false; + np->np_lpstate[np->np_lpndx].lp_enable = (result == OK); + return OK; + } + + /* Check if we are waiting for the condition associated with an until + * token. + * + * until ; do ; done + * + * Execute as long as has a non-zero exit + * status. + */ + + else if (np->np_lpstate[np->np_lpndx].lp_state == NSH_LOOP_UNTIL) + { + np->np_fail = false; + np->np_lpstate[np->np_lpndx].lp_enable = (result != OK); + return OK; + } + + /* Check if we are waiting for the condition associated with an if token */ + + else if (np->np_iestate[np->np_iendx].ie_state == NSH_ITEF_IF) { np->np_fail = false; np->np_iestate[np->np_iendx].ie_ifcond = result; @@ -1328,28 +1366,53 @@ static FAR char *nsh_argument(FAR struct nsh_vtbl_s *vtbl, FAR char **saveptr, } /**************************************************************************** - * Name: nsh_cmdenabled + * Name: nsh_loop_enabled ****************************************************************************/ #ifndef CONFIG_NSH_DISABLESCRIPT -static bool nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl) +static bool nsh_loop_enabled(FAR struct nsh_vtbl_s *vtbl) { - struct nsh_parser_s *np = &vtbl->np; + FAR struct nsh_parser_s *np = &vtbl->np; + + /* If we are looping and the disable bit is set, then we are skipping + * all data until we next get to the 'done' token at the end of the + * loop. + */ + + if (np->np_lpstate[np->np_lpndx].lp_state == NSH_LOOP_DO) + { + /* We have parsed 'do', looking for 'done' */ + + return (bool)np->np_lpstate[np->np_lpndx].lp_enable; + } + + return true; +} +#endif + +/**************************************************************************** + * Name: nsh_itef_enabled + ****************************************************************************/ + +#ifndef CONFIG_NSH_DISABLESCRIPT +static bool nsh_itef_enabled(FAR struct nsh_vtbl_s *vtbl) +{ + FAR struct nsh_parser_s *np = &vtbl->np; bool ret = !np->np_iestate[np->np_iendx].ie_disabled; if (ret) { switch (np->np_iestate[np->np_iendx].ie_state) { - case NSH_PARSER_NORMAL : - case NSH_PARSER_IF: + case NSH_ITEF_NORMAL: + case NSH_ITEF_IF: default: break; - case NSH_PARSER_THEN: + case NSH_ITEF_THEN: ret = !np->np_iestate[np->np_iendx].ie_ifcond; break; - case NSH_PARSER_ELSE: + case NSH_ITEF_ELSE: ret = np->np_iestate[np->np_iendx].ie_ifcond; break; } @@ -1360,12 +1423,218 @@ static bool nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl) #endif /**************************************************************************** - * Name: nsh_ifthenelse + * Name: nsh_cmdenabled + ****************************************************************************/ + +#ifndef CONFIG_NSH_DISABLESCRIPT +static bool nsh_cmdenabled(FAR struct nsh_vtbl_s *vtbl) +{ + /* Return true if command processing is enabled on this pass through the + * loop AND if command processing is enabled in this part of the if-then- + * else-fi sequence. + */ + + return (nsh_loop_enabled(vtbl) && nsh_itef_enabled(vtbl)); +} +#endif + +/**************************************************************************** + * Name: nsh_loop ****************************************************************************/ #ifndef CONFIG_NSH_DISABLESCRIPT -static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, - FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist) +static int nsh_loop(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, + FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist) +{ + FAR struct nsh_parser_s *np = &vtbl->np; + FAR char *cmd = *ppcmd; + long offset; + bool whilematch; + bool untilmatch; + bool enable; + int ret; + + if (cmd) + { + /* Check if the command is preceded by "while" or "until" */ + + whilematch = strcmp(cmd, "while"); + untilmatch = strcmp(cmd, "until"); + + if (whilematch == 0 || untilmatch == 0) + { + uint8_t state; + + /* Get the cmd following the "while" or "until" */ + + *ppcmd = nsh_argument(vtbl, saveptr, memlist); + if (!*ppcmd) + { + nsh_output(vtbl, g_fmtarginvalid, "if"); + goto errout; + } + + /* Verify that "while" or "until" is valid in this context */ + + if (np->np_iestate[np->np_iendx].ie_state == NSH_ITEF_IF || + np->np_lpstate[np->np_lpndx].lp_state == NSH_LOOP_WHILE || + np->np_lpstate[np->np_lpndx].lp_state == NSH_LOOP_UNTIL || + np->np_stream == NULL || np->np_foffs < 0) + { + nsh_output(vtbl, g_fmtcontext, cmd); + goto errout; + } + + /* Check if we have exceeded the maximum depth of nesting */ + + if (np->np_lpndx >= CONFIG_NSH_NESTDEPTH-1) + { + nsh_output(vtbl, g_fmtdeepnesting, cmd); + goto errout; + } + + /* "Push" the old state and set the new state */ + + state = whilematch == 0 ? NSH_LOOP_WHILE : NSH_LOOP_UNTIL; + enable = nsh_cmdenabled(vtbl); +#ifdef NSH_DISABLE_SEMICOLON + offset = np->np_foffs; +#else + offset = np->np_foffs + np->np_loffs; +#endif + +#ifndef NSH_DISABLE_SEMICOLON + np->np_jump = false; +#endif + np->np_lpndx++; + np->np_lpstate[np->np_lpndx].lp_state = state; + np->np_lpstate[np->np_lpndx].lp_enable = enable; + np->np_lpstate[np->np_lpndx].lp_topoffs = offset; + } + + /* Check if the token is "do" */ + + else if (strcmp(cmd, "do") == 0) + { + /* Get the cmd following the "do" -- there shouldn't be one */ + + *ppcmd = nsh_argument(vtbl, saveptr, memlist); + if (*ppcmd) + { + nsh_output(vtbl, g_fmtarginvalid, "do"); + goto errout; + } + + /* Verify that "do" is valid in this context */ + + if (np->np_lpstate[np->np_lpndx].lp_state != NSH_LOOP_WHILE && + np->np_lpstate[np->np_lpndx].lp_state != NSH_LOOP_UNTIL) + { + nsh_output(vtbl, g_fmtcontext, "do"); + goto errout; + } + + np->np_lpstate[np->np_lpndx].lp_state = NSH_LOOP_DO; + } + + /* Check if the token is "done" */ + + else if (strcmp(cmd, "done") == 0) + { + /* Get the cmd following the "done" -- there should be one */ + + *ppcmd = nsh_argument(vtbl, saveptr, memlist); + if (*ppcmd) + { + nsh_output(vtbl, g_fmtarginvalid, "done"); + goto errout; + } + + /* Verify that "done" is valid in this context */ + + if (np->np_lpstate[np->np_lpndx].lp_state != NSH_LOOP_DO) + { + nsh_output(vtbl, g_fmtcontext, "done"); + goto errout; + } + + if (np->np_lpndx < 1) /* Shouldn't happen */ + { + nsh_output(vtbl, g_fmtinternalerror, "done"); + goto errout; + } + + /* Now what do we do? We either: Do go back to the top of the + * loop (if lp_enable == true) or continue past the end of the + * loop (if lp_enable == false) + */ + + if (np->np_lpstate[np->np_lpndx].lp_enable) + { + /* Set the new file position to the top of the loop offset */ + + ret = fseek(np->np_stream, + np->np_lpstate[np->np_lpndx].lp_topoffs, + SEEK_SET); + if (ret < 0) + { + nsh_output(vtbl, g_fmtcmdfailed, "done", "fseek", NSH_ERRNO); + } + +#ifndef NSH_DISABLE_SEMICOLON + /* Signal nsh_parse that we need to stop processing the + * current line and jump back to the top of the loop. + */ + + np->np_jump = true; +#endif + } + else + { + np->np_lpstate[np->np_lpndx].lp_enable = true; + } + + /* "Pop" the previous state. We do this no matter what we + * decided to do + */ + + np->np_lpstate[np->np_lpndx].lp_state = NSH_LOOP_NORMAL; + np->np_lpndx--; + } + + /* If we just parsed "while" or "until", then nothing is acceptable + * other than "do" + */ + + else if (np->np_lpstate[np->np_lpndx].lp_state == NSH_LOOP_WHILE || + np->np_lpstate[np->np_lpndx].lp_state == NSH_LOOP_UNTIL) + { + nsh_output(vtbl, g_fmtcontext, cmd); + goto errout; + } + } + + return OK; + +errout: +#ifndef NSH_DISABLE_SEMICOLON + np->np_jump = false; +#endif + np->np_lpndx = 0; + np->np_lpstate[0].lp_state = NSH_LOOP_NORMAL; + np->np_lpstate[0].lp_enable = true; + np->np_lpstate[0].lp_topoffs = 0; + return ERROR; +} +#endif + +/**************************************************************************** + * Name: nsh_itef + ****************************************************************************/ + +#ifndef CONFIG_NSH_DISABLESCRIPT +static int nsh_itef(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, + FAR char **saveptr, FAR NSH_MEMLIST_TYPE *memlist) { FAR struct nsh_parser_s *np = &vtbl->np; FAR char *cmd = *ppcmd; @@ -1388,9 +1657,7 @@ static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, /* Verify that "if" is valid in this context */ - if (np->np_iestate[np->np_iendx].ie_state != NSH_PARSER_NORMAL && - np->np_iestate[np->np_iendx].ie_state != NSH_PARSER_THEN && - np->np_iestate[np->np_iendx].ie_state != NSH_PARSER_ELSE) + if (np->np_iestate[np->np_iendx].ie_state == NSH_ITEF_IF) { nsh_output(vtbl, g_fmtcontext, "if"); goto errout; @@ -1408,16 +1675,16 @@ static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, disabled = !nsh_cmdenabled(vtbl); np->np_iendx++; - np->np_iestate[np->np_iendx].ie_state = NSH_PARSER_IF; + np->np_iestate[np->np_iendx].ie_state = NSH_ITEF_IF; np->np_iestate[np->np_iendx].ie_disabled = disabled; np->np_iestate[np->np_iendx].ie_ifcond = false; } - /* Check if the command is "then" */ + /* Check if the token is "then" */ else if (strcmp(cmd, "then") == 0) { - /* Get the cmd following the then -- there shouldn't be one */ + /* Get the cmd following the "then" -- there shouldn't be one */ *ppcmd = nsh_argument(vtbl, saveptr, memlist); if (*ppcmd) @@ -1428,20 +1695,20 @@ static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, /* Verify that "then" is valid in this context */ - if (np->np_iestate[np->np_iendx].ie_state != NSH_PARSER_IF) + if (np->np_iestate[np->np_iendx].ie_state != NSH_ITEF_IF) { nsh_output(vtbl, g_fmtcontext, "then"); goto errout; } - np->np_iestate[np->np_iendx].ie_state = NSH_PARSER_THEN; + np->np_iestate[np->np_iendx].ie_state = NSH_ITEF_THEN; } - /* Check if the command is "else" */ + /* Check if the token is "else" */ else if (strcmp(cmd, "else") == 0) { - /* Get the cmd following the else -- there shouldn't be one */ + /* Get the cmd following the "else" -- there shouldn't be one */ *ppcmd = nsh_argument(vtbl, saveptr, memlist); if (*ppcmd) @@ -1452,16 +1719,16 @@ static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, /* Verify that "else" is valid in this context */ - if (np->np_iestate[np->np_iendx].ie_state != NSH_PARSER_THEN) + if (np->np_iestate[np->np_iendx].ie_state != NSH_ITEF_THEN) { nsh_output(vtbl, g_fmtcontext, "else"); goto errout; } - np->np_iestate[np->np_iendx].ie_state = NSH_PARSER_ELSE; + np->np_iestate[np->np_iendx].ie_state = NSH_ITEF_ELSE; } - /* Check if the command is "fi" */ + /* Check if the token is "fi" */ else if (strcmp(cmd, "fi") == 0) { @@ -1476,8 +1743,8 @@ static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, /* Verify that "fi" is valid in this context */ - if (np->np_iestate[np->np_iendx].ie_state != NSH_PARSER_THEN && - np->np_iestate[np->np_iendx].ie_state != NSH_PARSER_ELSE) + if (np->np_iestate[np->np_iendx].ie_state != NSH_ITEF_THEN && + np->np_iestate[np->np_iendx].ie_state != NSH_ITEF_ELSE) { nsh_output(vtbl, g_fmtcontext, "fi"); goto errout; @@ -1494,9 +1761,9 @@ static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, np->np_iendx--; } - /* If we just parsed 'if', then nothing is acceptable other than 'then' */ + /* If we just parsed "if", then nothing is acceptable other than "then" */ - else if (np->np_iestate[np->np_iendx].ie_state == NSH_PARSER_IF) + else if (np->np_iestate[np->np_iendx].ie_state == NSH_ITEF_IF) { nsh_output(vtbl, g_fmtcontext, cmd); goto errout; @@ -1507,7 +1774,7 @@ static int nsh_ifthenelse(FAR struct nsh_vtbl_s *vtbl, FAR char **ppcmd, errout: np->np_iendx = 0; - np->np_iestate[0].ie_state = NSH_PARSER_NORMAL; + np->np_iestate[0].ie_state = NSH_ITEF_NORMAL; np->np_iestate[0].ie_disabled = false; np->np_iestate[0].ie_ifcond = false; return ERROR; @@ -1724,10 +1991,18 @@ static int nsh_parse_command(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) saveptr = cmdline; cmd = nsh_argument(vtbl, &saveptr, &memlist); - /* Handler if-then-else-fi */ - #ifndef CONFIG_NSH_DISABLESCRIPT - if (nsh_ifthenelse(vtbl, &cmd, &saveptr, &memlist) != 0) + /* Handle while-do-done and until-do-done loops */ + + if (nsh_loop(vtbl, &cmd, &saveptr, &memlist) != 0) + { + NSH_MEMLIST_FREE(&memlist); + return nsh_saveresult(vtbl, true); + } + + /* Handle if-then-else-fi */ + + if (nsh_itef(vtbl, &cmd, &saveptr, &memlist) != 0) { NSH_MEMLIST_FREE(&memlist); return nsh_saveresult(vtbl, true); @@ -1872,16 +2147,31 @@ int nsh_parse(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) return nsh_parse_command(vtbl, cmdline); #else +#ifndef CONFIG_NSH_DISABLESCRIPT + FAR struct nsh_parser_s *np = &vtbl->np; +#endif FAR char *start = cmdline; FAR char *working = cmdline; FAR char *ptr; size_t len; int ret; - /* Loop until all of the commands on the command line have been processed */ + /* Loop until all of the commands on the command line have been processed OR + * until the end-of-loop has been recountered and we need to reload the line + * at the top of the loop. + */ +#ifndef CONFIG_NSH_DISABLESCRIPT + for (np->np_jump = false; !np->np_jump; ) +#else for (;;) +#endif { +#ifndef CONFIG_NSH_DISABLESCRIPT + /* Save the offset on the line to the start of the command */ + + np->np_loffs = (uint16_t)(working - cmdline); +#endif /* A command may be terminated with a newline character, the end of the * line, or a semicolon. NOTE that the set of delimiting characters * includes the quotation mark. We need to handle quotation marks here @@ -1955,5 +2245,9 @@ int nsh_parse(FAR struct nsh_vtbl_s *vtbl, FAR char *cmdline) working = ++tmp; } } + +#ifndef CONFIG_NSH_DISABLESCRIPT + return OK; +#endif #endif } diff --git a/apps/nshlib/nsh_script.c b/apps/nshlib/nsh_script.c index 088df6878..e5c1958a1 100644 --- a/apps/nshlib/nsh_script.c +++ b/apps/nshlib/nsh_script.c @@ -132,6 +132,23 @@ int nsh_script(FAR struct nsh_vtbl_s *vtbl, FAR const char *cmd, /* Get the next line of input from the file */ fflush(stdout); + + /* Get the current file position. This is used to control + * looping. If a loop begins in the next line, then this file + * offset will be needed to locate the top of the loop in the + * script file. Note that ftell will return -1 on failure. + */ + + vtbl->np.np_foffs = ftell(vtbl->np.np_stream); + vtbl->np.np_loffs = 0; + + if (vtbl->np.np_foffs < 0) + { + nsh_output(vtbl, g_fmtcmdfailed, "loop", "ftell", NSH_ERRNO); + } + + /* Now read the next line from the script file */ + pret = fgets(buffer, CONFIG_NSH_LINELEN, vtbl->np.np_stream); if (pret) { diff --git a/nuttx/Documentation/NuttShell.html b/nuttx/Documentation/NuttShell.html index c35cb371d..603ae759d 100644 --- a/nuttx/Documentation/NuttShell.html +++ b/nuttx/Documentation/NuttShell.html @@ -8,7 +8,7 @@

NuttShell (NSH)

-

Last Updated: January 10, 2014

+

Last Updated: January 17, 2014

@@ -52,25 +52,31 @@
- 1.4 Built-In Variables + 1.4 Looping
- 1.5 Current Working Directory + 1.5 Built-In Variables
- 1.6 Environment Variables + 1.6 Current Working Directory
- 1.7 NSH Start-Up Script + 1.7 Environment Variables + + + +
+ + 1.8 NSH Start-Up Script @@ -605,7 +611,28 @@ fi + +
-

1.4 Built-In Variables

+

1.4 Looping

+
+ +

+ while-do-done and until-do-done looping constructs are also supported. + These works from the command line but are primarily intended for use within NSH scripts + (see the sh command). + The syntax is as follows: +

+
    +
    while <test-cmd>; do <cmd-sequence>; done
    +
    Execute <cmd-sequence> as long as <test-cmd> has an exit status of zero.
    +
    until <test-cmd>; do <cmd-sequence>; done
    +
    Execute <cmd-sequence> as long as <test-cmd> has a non-zero exit status.
    +
+ + + +
+

1.5 Built-In Variables

@@ -623,7 +650,7 @@ fi
-

1.5 Current Working Directory

+

1.6 Current Working Directory

@@ -641,7 +668,7 @@ fi
-

1.6 Environment Variables

+

1.7 Environment Variables

@@ -661,7 +688,7 @@ fi
-

1.7 NSH Start-Up Script

+

1.8 NSH Start-Up Script

-- cgit v1.2.3