text: /*
   * (C) Copyright 2000
   * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
   *
   * Add to readline cmdline-editing by
   * (C) Copyright 2005
   * JinHua Luo, GuangDong Linux Center, <luo.jinhua@gd-linux.com>
   *
   * SPDX-License-Identifier:	GPL-2.0+
   */
  
  #include <common.h>
  #include <bootretry.h>
  #include <cli.h>
  #include <linux/ctype.h>
  
  #define DEBUG_PARSER	0	/* set to 1 to debug */
  
  #define debug_parser(fmt, args...)		\
  	debug_cond(DEBUG_PARSER, fmt, ##args)
  
  
  int cli_simple_parse_line(char *line, char *argv[])
  {
  	int nargs = 0;
  
  	debug_parser("%s: \"%s\"\n", __func__, line);
  	while (nargs < CONFIG_SYS_MAXARGS) {
  		/* skip any white space */
  		while (isblank(*line))
  			++line;
  
  		if (*line == '\0') {	/* end of line, no more args	*/
  			argv[nargs] = NULL;
  			debug_parser("%s: nargs=%d\n", __func__, nargs);
  			return nargs;
  		}
  
  		argv[nargs++] = line;	/* begin of argument string	*/
  
  		/* find end of string */
  		while (*line && !isblank(*line))
  			++line;
  
  		if (*line == '\0') {	/* end of line, no more args	*/
  			argv[nargs] = NULL;
  			debug_parser("parse_line: nargs=%d\n", nargs);
  			return nargs;
  		}
  
  		*line++ = '\0';		/* terminate current arg	 */
  	}
  
  	printf("** Too many args (max. %d) **\n", CONFIG_SYS_MAXARGS);
  
  	debug_parser("%s: nargs=%d\n", __func__, nargs);
  	return nargs;
  }
  
  static void process_macros(const char *input, char *output)
  {
  	char c, prev;
  	const char *varname_start = NULL;
  	int inputcnt = strlen(input);
  	int outputcnt = CONFIG_SYS_CBSIZE;
  	int state = 0;		/* 0 = waiting for '$'  */
  
  	/* 1 = waiting for '(' or '{' */
  	/* 2 = waiting for ')' or '}' */
  	/* 3 = waiting for '''  */
  	char *output_start = output;
  
  	debug_parser("[PROCESS_MACROS] INPUT len %zd: \"%s\"\n", strlen(input),
  		     input);
  
  	prev = '\0';		/* previous character   */
  
  	while (inputcnt && outputcnt) {
  		c = *input++;
  		inputcnt--;
  
  		if (state != 3) {
  			/* remove one level of escape characters */
  			if ((c == '\\') && (prev != '\\')) {
  				if (inputcnt-- == 0)
  					break;
  				prev = c;
  				c = *input++;
  			}
  		}
  
  		switch (state) {
  		case 0:	/* Waiting for (unescaped) $    */
  			if ((c == '\'') && (prev != '\\')) {
  				state = 3;
  				break;
  			}
  			if ((c == '$') && (prev != '\\')) {
  				state++;
  			} else {
  				*(output++) = c;
  				outputcnt--;
  			}
  			break;
  		case 1:	/* Waiting for (        */
  			if (c == '(' || c == '{') {
  				state++;
  				varname_start = input;
  			} else {
  				state = 0;
  				*(output++) = '$';
  				outputcnt--;
  
  				if (outputcnt) {
  					*(output++) = c;
  					outputcnt--;
  				}
  			}
  			break;
  		case 2:	/* Waiting for )        */
  			if (c == ')' || c == '}') {
  				int i;
  				char envname[CONFIG_SYS_CBSIZE], *envval;
  				/* Varname # of chars */
  				int envcnt = input - varname_start - 1;
  
  				/* Get the varname */
  				for (i = 0; i < envcnt; i++)
  					envname[i] = varname_start[i];
  				envname[i] = 0;
  
  				/* Get its value */
  				envval = getenv(envname);
  
  				/* Copy into the line if it exists */
  				if (envval != NULL)
  					while ((*envval) && outputcnt) {
  						*(output++) = *(envval++);
  						outputcnt--;
  					}
  				/* Look for another '$' */
  				state = 0;
  			}
  			break;
  		case 3:	/* Waiting for '        */
  			if ((c == '\'') && (prev != '\\')) {
  				state = 0;
  			} else {
  				*(output++) = c;
  				outputcnt--;
  			}
  			break;
  		}
  		prev = c;
  	}
  
  	if (outputcnt)
  		*output = 0;
  	else
  		*(output - 1) = 0;
  
  	debug_parser("[PROCESS_MACROS] OUTPUT len %zd: \"%s\"\n",
  		     strlen(output_start), output_start);
  }
  
   /*
   * WARNING:
   *
   * We must create a temporary copy of the command since the command we get
   * may be the result from getenv(), which returns a pointer directly to
   * the environment data, which may change magicly when the command we run
   * creates or modifies environment variables (like "bootp" does).
   */
  int cli_simple_run_command(const char *cmd, int flag)
  {
  	char cmdbuf[CONFIG_SYS_CBSIZE];	/* working copy of cmd		*/
  	char *token;			/* start of token in cmdbuf	*/
  	char *sep;			/* end of token (separator) in cmdbuf */
  	char finaltoken[CONFIG_SYS_CBSIZE];
  	char *str = cmdbuf;
  	char *argv[CONFIG_SYS_MAXARGS + 1];	/* NULL terminated	*/
  	int argc, inquotes;
  	int repeatable = 1;
  	int rc = 0;
  
  	debug_parser("[RUN_COMMAND] cmd[%p]=\"", cmd);
  	if (DEBUG_PARSER) {
  		/* use puts - string may be loooong */
  		puts(cmd ? cmd : "NULL");
  		puts("\"\n");
  	}
  	clear_ctrlc();		/* forget any previous Control C */
  
  	if (!cmd || !*cmd)
  		return -1;	/* empty command */
  
  	if (strlen(cmd) >= CONFIG_SYS_CBSIZE) {
  		puts("## Command too long!\n");
  		return -1;
  	}
  
  	strcpy(cmdbuf, cmd);
  
  	/* Process separators and check for invalid
  	 * repeatable commands
  	 */
  
  	debug_parser("[PROCESS_SEPARATORS] %s\n", cmd);
  	while (*str) {
  		/*
  		 * Find separator, or string end
  		 * Allow simple escape of ';' by writing "\;"
  		 */
  		for (inquotes = 0, sep = str; *sep; sep++) {
  			if ((*sep == '\'') &&
  			    (*(sep - 1) != '\\'))
  				inquotes = !inquotes;
  
  			if (!inquotes &&
  			    (*sep == ';') &&	/* separator		*/
  			    (sep != str) &&	/* past string start	*/
  			    (*(sep - 1) != '\\'))	/* and NOT escaped */
  				break;
  		}
  
  		/*
  		 * Limit the token to data between separators
  		 */
  		token = str;
  		if (*sep) {
  			str = sep + 1;	/* start of command for next pass */
  			*sep = '\0';
  		} else {
  			str = sep;	/* no more commands for next pass */
  		}
  		debug_parser("token: \"%s\"\n", token);
  
  		/* find macros in this token and replace them */
  		process_macros(token, finaltoken);
  
  		/* Extract arguments */
  		argc = cli_simple_parse_line(finaltoken, argv);
  		if (argc == 0) {
  			rc = -1;	/* no command at all */
  			continue;
  		}
  
  		if (cmd_process(flag, argc, argv, &repeatable, NULL))
  			rc = -1;
  
  		/* Did the user stop this? */
  		if (had_ctrlc())
  			return -1;	/* if stopped then not repeatable */
  	}
  
  	return rc ? rc : repeatable;
  }
  
  void cli_simple_loop(void)
  {
  	static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
  
  	int len;
  	int flag;
  	int rc = 1;
  
  	for (;;) {
  		if (rc >= 0) {
  			/* Saw enough of a valid command to
  			 * restart the timeout.
  			 */
  			bootretry_reset_cmd_timeout();
  		}
  		len = cli_readline(CONFIG_SYS_PROMPT);
  
  		flag = 0;	/* assume no special flags for now */
  		if (len > 0)
  			strcpy(lastcommand, console_buffer);
  		else if (len == 0)
  			flag |= CMD_FLAG_REPEAT;
  #ifdef CONFIG_BOOT_RETRY_TIME
  		else if (len == -2) {
  			/* -2 means timed out, retry autoboot
  			 */
  			puts("\nTimed out waiting for command\n");
  # ifdef CONFIG_RESET_TO_RETRY
  			/* Reinit board to run initialization code again */
  			do_reset(NULL, 0, 0, NULL);
  # else
  			return;		/* retry autoboot */
  # endif
  		}
  #endif
  
  		if (len == -1)
  			puts("<INTERRUPT>\n");
  		else
  			rc = run_command_repeatable(lastcommand, flag);
  
  		if (rc <= 0) {
  			/* invalid command or not repeatable, forget it */
  			lastcommand[0] = 0;
  		}
  	}
  }
  
  int cli_simple_run_command_list(char *cmd, int flag)
  {
  	char *line, *next;
  	int rcode = 0;
  
  	/*
  	 * Break into individual lines, and execute each line; terminate on
  	 * error.
  	 */
  	next = cmd;
  	line = cmd;
  	while (*next) {
  		if (*next == '\n') {
  			*next = '\0';
  			/* run only non-empty commands */
  			if (*line) {
  				debug("** exec: \"%s\"\n", line);
  				if (cli_simple_run_command(line, 0) < 0) {
  					rcode = 1;
  					break;
  				}
  			}
  			line = next + 1;
  		}
  		++next;
  	}
  	if (rcode == 0 && *line)
  		rcode = (cli_simple_run_command(line, 0) < 0);
  
  	return rcode;
  }
  