/*
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/* Thi file is to parse the simScriptPos, function file and default file,
 *
 * syntax
 *
 *    expression is format like:
 *       $var [=|<|>|==] var
 *     or:
 *       $var = var [+|-|*|/|.] var
 *
 *    if expression
 *      command
 *
 *    if expression then
 *      commands
 *    elif expression
 *      commands
 *    elif expression
 *      commands
 *    else
 *      commands
 *    endi
 *
 *    while expression
 *      commands
 *      continue
 *      break
 *    endw
 *
 *    switch expression
 *      case 1
 *        commands
 *        break
 *      case 2
 *        commands
 *        break
 *      default
 *        commands
 *    ends
 *
 *    label:
 *    goto label
 *
 */

#define _DEFAULT_SOURCE
#include "simParse.h"

static SCommand *cmdHashList[MAX_NUM_CMD];
static SCmdLine  cmdLine[MAX_CMD_LINES];
static char      parseErr[MAX_ERROR_LEN];
static char      optionBuffer[MAX_OPTION_BUFFER];
static int32_t   numOfLines, optionOffset;
static SLabel    label, dest;
static SBlock    block;

int32_t simHashCmd(char *token, int32_t tokenLen) {
  int32_t i;
  int32_t hash = 0;

  for (i = 0; i < tokenLen; ++i) hash += token[i];

  hash = hash % MAX_NUM_CMD;

  return hash;
}

SCommand *simCheckCmd(char *token, int32_t tokenLen) {
  int32_t   hash;
  SCommand *node;

  hash = simHashCmd(token, tokenLen);

  node = cmdHashList[hash];

  while (node) {
    if (node->nlen == tokenLen && strncmp(node->name, token, tokenLen) == 0) {
      return node;
    } else {
      node = node->next;
    }
  }

  return NULL;
}

void simAddCmdIntoHash(SCommand *pCmd) {
  int32_t   hash;
  SCommand *node;

  hash = simHashCmd(pCmd->name, (int32_t)strlen(pCmd->name));
  node = cmdHashList[hash];
  pCmd->next = node;
  cmdHashList[hash] = pCmd;
}

void simResetParser() {
  optionOffset = 4;
  numOfLines = 0;
  memset(cmdLine, 0, sizeof(cmdLine));
  memset(optionBuffer, 0, sizeof(optionBuffer));
  memset(&label, 0, sizeof(label));
  memset(&block, 0, sizeof(block));
  memset(&dest, 0, sizeof(dest));
}

SScript *simBuildScriptObj(char *fileName) {
  int32_t i, destPos;

  /* process labels */

  cmdLine[numOfLines].cmdno = SIM_CMD_RETURN;
  numOfLines++;

  for (i = 0; i < numOfLines; ++i) {
    cmdLine[i].errorJump = SQL_JUMP_FALSE;
  }

  for (--dest.top; dest.top >= 0; --dest.top) {
    for (i = 0; i < label.top; ++i) {
      if (strcmp(label.label[i], dest.label[(uint8_t)dest.top]) == 0) break;
    }

    if (i == label.top) {
      sprintf(parseErr, "label:%s not defined", dest.label[(uint8_t)dest.top]);
      return NULL;
    }

    destPos = dest.pos[(uint8_t)dest.top];
    cmdLine[destPos].jump = label.pos[i];
    if (cmdLine[destPos].cmdno == SIM_CMD_SQL) {
      cmdLine[destPos].errorJump = SQL_JUMP_TRUE;
    }
  }

  if (block.top != 0) {
    sprintf(parseErr, "mismatched block");
    return NULL;
  }

  for (i = 0; i < numOfLines; ++i) {
    if (cmdLine[i].jump == 0) cmdLine[i].jump = numOfLines;
  }

  SScript *script = taosMemoryMalloc(sizeof(SScript));
  memset(script, 0, sizeof(SScript));

  script->type = SIM_SCRIPT_TYPE_MAIN;
  script->numOfLines = numOfLines;
  tstrncpy(script->fileName, fileName, sizeof(script->fileName));

  script->optionBuffer = taosMemoryMalloc(optionOffset);
  memcpy(script->optionBuffer, optionBuffer, optionOffset);

  script->lines = taosMemoryMalloc(sizeof(SCmdLine) * numOfLines);
  memcpy(script->lines, cmdLine, sizeof(SCmdLine) * numOfLines);

  return script;
}

SScript *simParseScript(char *fileName) {
  TdFilePtr pFile;
  int32_t   tokenLen, lineNum = 0;
  char      buffer[10 * 1024] = {0};
  char      name[PATH_MAX] = {9};
  char     *token = NULL;
  char     *rest = NULL;
  SCommand *pCmd = NULL;
  SScript  *script = NULL;

  if ((fileName[0] == '.') || (fileName[0] == '/')) {
    tstrncpy(name, fileName, PATH_MAX);
  } else {
    snprintf(name, PATH_MAX, "%s" TD_DIRSEP "%s", simScriptDir, fileName);
    taosRealPath(name, NULL, sizeof(name));
  }

  // if ((fd = fopen(name, "r")) == NULL) {
  if ((pFile = taosOpenFile(name, TD_FILE_READ | TD_FILE_STREAM)) == NULL) {
    simError("failed to open file:%s", name);
    return NULL;
  }

  simResetParser();

  while (!taosEOFFile(pFile)) {
    if (taosGetsFile(pFile, sizeof(buffer) - 1, buffer) == -1) continue;

    lineNum++;
    int32_t cmdlen = (int32_t)strlen(buffer);
    if (buffer[cmdlen - 1] == '\r' || buffer[cmdlen - 1] == '\n') {
      buffer[cmdlen - 1] = 0;
    }
    rest = buffer;

    for (int32_t i = 0; i < cmdlen; ++i) {
      if (buffer[i] == '\r' || buffer[i] == '\n') {
        buffer[i] = '\0';
      }
    }

  again:
    rest = paGetToken(rest, &token, &tokenLen);
    if (tokenLen == 0) continue;

    if (token[tokenLen - 1] == ':') {
      strncpy(label.label[(uint8_t)label.top], token, tokenLen - 1);
      label.pos[(uint8_t)label.top] = numOfLines;
      label.top++;
      goto again;
    }

    if (token[0] == '$') {
      if (!simParseExpression(token, lineNum)) {
        simError("script:%s line:%d %s", fileName, lineNum, parseErr);
        return NULL;
      }
      continue;
    }

    if ((pCmd = simCheckCmd(token, tokenLen)) == NULL) {
      token[tokenLen] = 0;
      simError("script:%s line:%d invalid cmd:%s", fileName, lineNum, token);
      return NULL;
    }

    if (!pCmd->parseCmd(rest, pCmd, lineNum)) {
      simError("script:%s line:%d %s", fileName, lineNum, parseErr);
      return NULL;
    }
  }
  taosCloseFile(&pFile);

  script = simBuildScriptObj(fileName);
  if (script == NULL) simError("script:%s %s", fileName, parseErr);

  return script;
}

int32_t simCheckExpression(char *exp) {
  char   *op1, *op2, *op, *rest;
  int32_t op1Len, op2Len, opLen;

  rest = paGetToken(exp, &op1, &op1Len);
  if (op1Len == 0) {
    sprintf(parseErr, "expression is required");
    return -1;
  }

  rest = paGetToken(rest, &op, &opLen);
  if (opLen == 0) {
    sprintf(parseErr, "operator is missed");
    return -1;
  }

  rest = paGetToken(rest, &op2, &op2Len);
  if (op2Len == 0) {
    sprintf(parseErr, "operand is missed");
    return -1;
  }

  if (opLen == 1) {
    if (op[0] != '=' && op[0] != '<' && op[0] != '>') {
      sprintf(parseErr, "invalid operator:%s", op);
      return -1;
    }

    if (op[0] == '=' && op1[0] != '$') {
      sprintf(parseErr, "left side of assignment must be variable");
      return -1;
    }
  } else if (opLen == 2) {
    if (op[1] != '=' || (op[0] != '=' && op[0] != '<' && op[0] != '>' && op[0] != '!')) {
      sprintf(parseErr, "left side of assignment must be variable");
      return -1;
    }
  } else {
    sprintf(parseErr, "invalid operator:%s", op);
    return -1;
  }

  rest = paGetToken(rest, &op, &opLen);

  if (opLen == 0) return (int32_t)(rest - exp);

  /* if it is key word "then" */
  if (strncmp(op, "then", 4) == 0) return (int32_t)(op - exp);

  rest = paGetToken(rest, &op2, &op2Len);
  if (op2Len == 0) {
    sprintf(parseErr, "operand is missed");
    return -1;
  }

  if (opLen > 1) {
    sprintf(parseErr, "invalid operator:%s", op);
    return -1;
  }

  if (op[0] == '+' || op[0] == '-' || op[0] == '*' || op[0] == '/' || op[0] == '.') {
    return (int32_t)(rest - exp);
  }

  return -1;
}

bool simParseExpression(char *token, int32_t lineNum) {
  int32_t expLen;

  expLen = simCheckExpression(token);
  if (expLen <= 0) return -1;

  cmdLine[numOfLines].cmdno = SIM_CMD_EXP;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  memcpy(optionBuffer + optionOffset, token, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}

bool simParseIfCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  char   *ret;
  int32_t expLen;

  expLen = simCheckExpression(rest);

  if (expLen <= 0) return -1;

  ret = rest + expLen;

  if (strncmp(ret, "then", 4) == 0) {
    block.type[(uint8_t)block.top] = BLOCK_IF;
    block.pos[(uint8_t)block.top] = &cmdLine[numOfLines].jump;
    block.top++;
  } else {
    cmdLine[numOfLines].jump = numOfLines + 2;
  }

  cmdLine[numOfLines].optionOffset = optionOffset;
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;
  cmdLine[numOfLines].cmdno = SIM_CMD_TEST;
  cmdLine[numOfLines].lineNum = lineNum;

  numOfLines++;
  return true;
}

bool simParseElifCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t expLen;

  expLen = simCheckExpression(rest);

  if (expLen <= 0) return -1;

  if (block.top < 1) {
    sprintf(parseErr, "no matching if");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_IF) {
    sprintf(parseErr, "no matched if block");
    return false;
  }

  cmdLine[numOfLines].cmdno = SIM_CMD_GOTO;
  block.jump[block.top - 1][(uint8_t)block.numJump[block.top - 1]] = &(cmdLine[numOfLines].jump);
  block.numJump[block.top - 1]++;

  numOfLines++;

  *(block.pos[block.top - 1]) = numOfLines;
  block.pos[block.top - 1] = &cmdLine[numOfLines].jump;

  cmdLine[numOfLines].optionOffset = optionOffset;
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;
  cmdLine[numOfLines].cmdno = SIM_CMD_TEST;
  cmdLine[numOfLines].lineNum = lineNum;

  numOfLines++;
  return true;
}

bool simParseElseCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  if (block.top < 1) {
    sprintf(parseErr, "no matching if");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_IF) {
    sprintf(parseErr, "no matched if block");
    return false;
  }

  cmdLine[numOfLines].cmdno = SIM_CMD_GOTO;
  block.jump[block.top - 1][(uint8_t)block.numJump[block.top - 1]] = &(cmdLine[numOfLines].jump);
  block.numJump[block.top - 1]++;

  numOfLines++;

  *(block.pos[block.top - 1]) = numOfLines;
  block.pos[block.top - 1] = NULL;

  return true;
}

bool simParseEndiCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t i;

  if (block.top < 1) {
    sprintf(parseErr, "no matching if");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_IF) {
    sprintf(parseErr, "no matched if block");
    return false;
  }

  if (block.pos[block.top - 1]) *(block.pos[block.top - 1]) = numOfLines;

  for (i = 0; i < block.numJump[block.top - 1]; ++i) {
    *(block.jump[block.top - 1][i]) = numOfLines;
  }

  block.numJump[block.top - 1] = 0;
  block.top--;

  return true;
}

bool simParseWhileCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t expLen;

  expLen = simCheckExpression(rest);

  if (expLen <= 0) return false;

  block.type[(uint8_t)block.top] = BLOCK_WHILE;
  block.pos[(uint8_t)block.top] = &(cmdLine[numOfLines].jump);
  block.back[(uint8_t)block.top] = numOfLines;
  block.top++;

  cmdLine[numOfLines].optionOffset = optionOffset;
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;
  cmdLine[numOfLines].cmdno = SIM_CMD_TEST;
  cmdLine[numOfLines].lineNum = lineNum;

  numOfLines++;
  return true;
}

bool simParseEndwCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t i;

  if (block.top < 1) {
    sprintf(parseErr, "no matching while");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_WHILE) {
    sprintf(parseErr, "no matched while block");
    return false;
  }

  cmdLine[numOfLines].cmdno = SIM_CMD_GOTO;
  cmdLine[numOfLines].jump = block.back[block.top - 1];
  cmdLine[numOfLines].lineNum = lineNum;
  numOfLines++;

  *(block.pos[block.top - 1]) = numOfLines;

  for (i = 0; i < block.numJump[block.top - 1]; ++i) {
    *(block.jump[block.top - 1][i]) = numOfLines;
  }

  block.top--;

  return true;
}

#if 0
bool simParseSwitchCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  char   *token;
  int32_t tokenLen;

  rest = paGetToken(rest, &token, &tokenLen);
  if (tokenLen == 0) {
    sprintf(parseErr, "switch should be followed by variable");
    return false;
  }

  if (token[0] != '$') {
    sprintf(parseErr, "switch must be followed by variable");
    return false;
  }

  memcpy(block.sexp[(uint8_t)block.top], token, tokenLen);
  block.sexpLen[(uint8_t)block.top] = tokenLen;
  block.type[(uint8_t)block.top] = BLOCK_SWITCH;
  block.top++;

  return true;
}

bool simParseCaseCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  char   *token;
  int32_t tokenLen;

  rest = paGetToken(rest, &token, &tokenLen);
  if (tokenLen == 0) {
    sprintf(parseErr, "case should be followed by value");
    return false;
  }

  if (block.top < 1) {
    sprintf(parseErr, "no matching switch");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_SWITCH) {
    sprintf(parseErr, "case not matched");
    return false;
  }

  if (block.pos[block.top - 1] != NULL) {
    *(block.pos[block.top - 1]) = numOfLines;
  }

  block.pos[block.top - 1] = &(cmdLine[numOfLines].jump);

  cmdLine[numOfLines].cmdno = SIM_CMD_TEST;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  memcpy(optionBuffer + optionOffset, block.sexp[block.top - 1], block.sexpLen[block.top - 1]);
  optionOffset += block.sexpLen[block.top - 1];
  *(optionBuffer + optionOffset++) = ' ';
  *(optionBuffer + optionOffset++) = '=';
  *(optionBuffer + optionOffset++) = '=';
  *(optionBuffer + optionOffset++) = ' ';
  memcpy(optionBuffer + optionOffset, token, tokenLen);
  optionOffset += tokenLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}

bool simParseBreakCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  if (block.top < 1) {
    sprintf(parseErr, "no blcok exists");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_SWITCH && block.type[block.top - 1] != BLOCK_WHILE) {
    sprintf(parseErr, "not in switch or while block");
    return false;
  }

  block.jump[block.top - 1][(uint8_t)block.numJump[block.top - 1]] = &(cmdLine[numOfLines].jump);
  block.numJump[block.top - 1]++;

  cmdLine[numOfLines].cmdno = SIM_CMD_GOTO;
  cmdLine[numOfLines].lineNum = lineNum;

  numOfLines++;
  return true;
}

bool simParseDefaultCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  if (block.top < 1) {
    sprintf(parseErr, "no matching switch");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_SWITCH) {
    sprintf(parseErr, "default should be matched with switch");
    return false;
  }

  if (block.pos[block.top - 1] != NULL) {
    *(block.pos[block.top - 1]) = numOfLines;
  }

  return true;
}

bool simParseEndsCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t i;

  if (block.top < 1) {
    sprintf(parseErr, "no matching switch");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_SWITCH) {
    sprintf(parseErr, "ends should be matched with switch");
    return false;
  }

  for (i = 0; i < block.numJump[block.top - 1]; ++i) {
    *(block.jump[block.top - 1][i]) = numOfLines;
  }

  block.numJump[block.top - 1] = 0;
  block.top--;

  return true;
}

bool simParseContinueCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  if (block.top < 1) {
    sprintf(parseErr, "no matching while");
    return false;
  }

  if (block.type[block.top - 1] != BLOCK_WHILE) {
    sprintf(parseErr, "continue should be matched with while cmd");
    return false;
  }

  cmdLine[numOfLines].cmdno = SIM_CMD_GOTO;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].jump = block.back[block.top - 1];

  numOfLines++;
  return true;
}
#endif

bool simParsePrintCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t expLen;

  rest++;
  cmdLine[numOfLines].cmdno = SIM_CMD_PRINT;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  expLen = (int32_t)strlen(rest);
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}

void simCheckSqlOption(char *rest) {
  int32_t valueLen;
  char   *value, *xpos;

  xpos = strstr(rest, " -x");  // need a blank
  if (xpos) {
    paGetToken(xpos + 3, &value, &valueLen);
    if (valueLen != 0) {
      memcpy(dest.label[(uint8_t)dest.top], value, valueLen);
      dest.label[(uint8_t)dest.top][valueLen] = 0;
      dest.pos[(uint8_t)dest.top] = numOfLines;
      dest.top++;

      *xpos = 0;
    }
  }
}

bool simParseSqlCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t expLen;

  rest++;
  simCheckSqlOption(rest);
  cmdLine[numOfLines].cmdno = SIM_CMD_SQL;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  expLen = (int32_t)strlen(rest);
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}

bool simParseSqlErrorCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t expLen;

  rest++;
  cmdLine[numOfLines].cmdno = SIM_CMD_SQL_ERROR;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  expLen = (int32_t)strlen(rest);
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}

#if 0
bool simParseSqlSlowCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  simParseSqlCmd(rest, pCmd, lineNum);
  cmdLine[numOfLines - 1].cmdno = SIM_CMD_SQL_SLOW;
  return true;
}

bool simParseRestfulCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  simParseSqlCmd(rest, pCmd, lineNum);
  cmdLine[numOfLines - 1].cmdno = SIM_CMD_RESTFUL;
  return true;
}
#endif

bool simParseSystemCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t expLen;

  rest++;
  cmdLine[numOfLines].cmdno = SIM_CMD_SYSTEM;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  expLen = (int32_t)strlen(rest);
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}

bool simParseSystemContentCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  simParseSystemCmd(rest, pCmd, lineNum);
  cmdLine[numOfLines - 1].cmdno = SIM_CMD_SYSTEM_CONTENT;
  return true;
}

bool simParseSleepCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  char   *token;
  int32_t tokenLen;

  cmdLine[numOfLines].cmdno = SIM_CMD_SLEEP;
  cmdLine[numOfLines].lineNum = lineNum;

  paGetToken(rest, &token, &tokenLen);
  if (tokenLen > 0) {
    cmdLine[numOfLines].optionOffset = optionOffset;
    memcpy(optionBuffer + optionOffset, token, tokenLen);
    optionOffset += tokenLen + 1;
    *(optionBuffer + optionOffset - 1) = 0;
  }

  numOfLines++;
  return true;
}

bool simParseReturnCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  char   *token;
  int32_t tokenLen;

  cmdLine[numOfLines].cmdno = SIM_CMD_RETURN;
  cmdLine[numOfLines].lineNum = lineNum;

  paGetToken(rest, &token, &tokenLen);
  if (tokenLen > 0) {
    cmdLine[numOfLines].optionOffset = optionOffset;
    memcpy(optionBuffer + optionOffset, token, tokenLen);
    optionOffset += tokenLen + 1;
    *(optionBuffer + optionOffset - 1) = 0;
  }

  numOfLines++;
  return true;
}

bool simParseGotoCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  char   *token;
  int32_t tokenLen;

  rest = paGetToken(rest, &token, &tokenLen);

  if (tokenLen == 0) {
    sprintf(parseErr, "label should be followed by goto cmd");
    return false;
  }

  memcpy(dest.label[(uint8_t)dest.top], token, tokenLen);
  dest.label[(uint8_t)dest.top][tokenLen] = 0;
  dest.pos[(uint8_t)dest.top] = numOfLines;
  dest.top++;

  cmdLine[numOfLines].cmdno = SIM_CMD_GOTO;
  cmdLine[numOfLines].lineNum = lineNum;

  numOfLines++;
  return true;
}

bool simParseRunCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  char   *token;
  int32_t tokenLen;

  rest = paGetToken(rest, &token, &tokenLen);

  if (tokenLen == 0) {
    sprintf(parseErr, "file name should be followed by run cmd");
    return false;
  }

  cmdLine[numOfLines].cmdno = SIM_CMD_RUN;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  memcpy(optionBuffer + optionOffset, token, tokenLen);
  optionOffset += tokenLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}

bool simParseRunBackCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  simParseRunCmd(rest, pCmd, lineNum);
  cmdLine[numOfLines - 1].cmdno = SIM_CMD_RUN_BACK;
  return true;
}

#if 0
bool simParseLineInsertCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t expLen;

  rest++;
  cmdLine[numOfLines].cmdno = SIM_CMD_LINE_INSERT;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  expLen = (int32_t)strlen(rest);
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}

bool simParseLineInsertErrorCmd(char *rest, SCommand *pCmd, int32_t lineNum) {
  int32_t expLen;

  rest++;
  cmdLine[numOfLines].cmdno = SIM_CMD_LINE_INSERT;
  cmdLine[numOfLines].lineNum = lineNum;
  cmdLine[numOfLines].optionOffset = optionOffset;
  expLen = (int32_t)strlen(rest);
  memcpy(optionBuffer + optionOffset, rest, expLen);
  optionOffset += expLen + 1;
  *(optionBuffer + optionOffset - 1) = 0;

  numOfLines++;
  return true;
}
#endif

void simInitsimCmdList() {
  int32_t cmdno;
  memset(simCmdList, 0, SIM_CMD_END * sizeof(SCommand));

  /* internal command */
  cmdno = SIM_CMD_EXP;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "exp");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = NULL;
  simCmdList[cmdno].executeCmd = simExecuteExpCmd;

  cmdno = SIM_CMD_IF;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "if");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseIfCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_ELIF;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "elif");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseElifCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_ELSE;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "else");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseElseCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_ENDI;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "endi");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseEndiCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_WHILE;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "while");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseWhileCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_ENDW;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "endw");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseEndwCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

#if 0
  cmdno = SIM_CMD_SWITCH;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "switch");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseSwitchCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_CASE;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "case");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseCaseCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_DEFAULT;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "default");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseDefaultCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_BREAK;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "break");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseBreakCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_CONTINUE;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "continue");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseContinueCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_ENDS;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "ends");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseEndsCmd;
  simCmdList[cmdno].executeCmd = NULL;
  simAddCmdIntoHash(&(simCmdList[cmdno]));
#endif

  cmdno = SIM_CMD_SLEEP;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "sleep");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseSleepCmd;
  simCmdList[cmdno].executeCmd = simExecuteSleepCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_GOTO;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "goto");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseGotoCmd;
  simCmdList[cmdno].executeCmd = simExecuteGotoCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_RUN;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "run");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseRunCmd;
  simCmdList[cmdno].executeCmd = simExecuteRunCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_RUN_BACK;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "run_back");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseRunBackCmd;
  simCmdList[cmdno].executeCmd = simExecuteRunBackCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_SYSTEM;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "system");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseSystemCmd;
  simCmdList[cmdno].executeCmd = simExecuteSystemCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_SYSTEM_CONTENT;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "system_content");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseSystemContentCmd;
  simCmdList[cmdno].executeCmd = simExecuteSystemContentCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_PRINT;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "print");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParsePrintCmd;
  simCmdList[cmdno].executeCmd = simExecutePrintCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_SQL;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "sql");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseSqlCmd;
  simCmdList[cmdno].executeCmd = simExecuteSqlCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_SQL_ERROR;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "sql_error");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseSqlErrorCmd;
  simCmdList[cmdno].executeCmd = simExecuteSqlErrorCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

#if 0
  cmdno = SIM_CMD_SQL_SLOW;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "sql_slow");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseSqlSlowCmd;
  simCmdList[cmdno].executeCmd = simExecuteSqlSlowCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_RESTFUL;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "restful");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseRestfulCmd;
  simCmdList[cmdno].executeCmd = simExecuteRestfulCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));
#endif

  /* test is only an internal command */
  cmdno = SIM_CMD_TEST;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "test");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = NULL;
  simCmdList[cmdno].executeCmd = simExecuteTestCmd;

  cmdno = SIM_CMD_RETURN;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "return");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseReturnCmd;
  simCmdList[cmdno].executeCmd = simExecuteReturnCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

#if 0
  cmdno = SIM_CMD_LINE_INSERT;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "line_insert");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseLineInsertCmd;
  simCmdList[cmdno].executeCmd = simExecuteLineInsertCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));

  cmdno = SIM_CMD_LINE_INSERT_ERROR;
  simCmdList[cmdno].cmdno = cmdno;
  strcpy(simCmdList[cmdno].name, "line_insert_error");
  simCmdList[cmdno].nlen = (int16_t)strlen(simCmdList[cmdno].name);
  simCmdList[cmdno].parseCmd = simParseLineInsertErrorCmd;
  simCmdList[cmdno].executeCmd = simExecuteLineInsertErrorCmd;
  simAddCmdIntoHash(&(simCmdList[cmdno]));
#endif
}
