// parse a script string
        static public scriptParserCmd parseString(string s)
        {
            // create a new cmd
            scriptParserCmd cmd = new scriptParserCmd();

            cmd.error  = true;
            cmd.strCmd = s;

            // is comment?
            if (isComment(s))
            {
                cmd.cmd   = cmdType.comment;
                cmd.error = false;
                return(cmd);
            }

            // try to parse
            var reg = Regex.Match(s, @"\b(\w+)\s*\({1}\s*(.*)\)\s*;");

            if (reg.Success)
            {
                string funcName   = reg.Groups[1].Value.ToLower();
                string funcParams = reg.Groups[2].Value;

                // try to parse as sleep cmd
                cmd = parseSleep(funcName, funcParams);
                // try to parse as send cmd
                if (cmd.error)
                {
                    cmd = parseSend(funcName, funcParams);
                }
                // try to parse as read cmd
                if (cmd.error)
                {
                    cmd = parseRead(s, funcName, funcParams);
                }
                // try to parse as trace cmd
                if (cmd.error)
                {
                    cmd = parseTrace(funcName, funcParams);
                }
                // breakpoint
                if (cmd.error)
                {
                    cmd = parseBreakpoint(funcName, funcParams);
                }
                // try to parse as send uds request cmd
                if (cmd.error)
                {
                    cmd = parseSendUds(s, funcName, funcParams);
                }
                if (cmd.error)
                {
                    cmd = parseSendBmw(s, funcName, funcParams);
                }
            }

            cmd.strCmd = s;
            return(cmd);
        }
        // parser
        static public scriptParserCmd[] parseText(string txt)
        {
            // replace '\t'
            txt = txt.Replace('\t', ' ');
            // remove comments
            txt = removeComments(txt);
            // remove #if 0 else #endif
            txt = removeIfElse(txt);
            // remove #if 0
            txt = removeIf0(txt);
            // defines, as int = ..
            txt = applyDefines(txt);
            // connect strings
            txt = connectStrings(txt);

            // split
            string[] ls = txt.Split('\n');
            for (int i = 0; i < ls.Count(); i++)
            {
                ls[i] += "\n";
            }

            // parse
            scriptParserCmd[] cmd = new scriptParserCmd[ls.Length];
            for (int i = 0; i < ls.Length; i++)
            {
                cmd[i] = parseString(ls[i]);
            }

            return(cmd);
        }
        // parser: breakpoint
        static private scriptParserCmd parseBreakpoint(string funcName, string args)
        {
            // create a new cmd
            scriptParserCmd cmd = new scriptParserCmd();

            cmd.error = true;

            if (funcName == "breakpoint" && string.IsNullOrEmpty(args))
            {
                cmd.error = false;
                cmd.cmd   = cmdType.breakpoint;
            }

            return(cmd);
        }
        // parser: sleep message
        static private scriptParserCmd parseSleep(string funcName, string args)
        {
            // create a new cmd
            scriptParserCmd cmd = new scriptParserCmd();

            cmd.error = true;

            if (funcName == "sleep")
            {
                int val = 0;
                if (canAnalyzer.Tools.tryParseInt(args, out val))
                {
                    // complete
                    cmd.error    = false;
                    cmd.sleepTmo = val;
                    cmd.cmd      = cmdType.sleep;
                }
            }
            return(cmd);
        }
        // format:  bmw_req(ecu_id, data..);
        // example: buff2 = bmw_req(0x60, 0x22, 0xF1, 0x90);
        static private scriptParserCmd parseSendBmw(string inString, string funcName, string args)
        {
            // create a new cmd
            scriptParserCmd cmd = new scriptParserCmd();

            cmd.error = true;

            // remove spaces
            args = args.Replace(" ", "");

            if (funcName == "bmw_req" || funcName == "bmw_req11")
            {
                bool asVar  = false;
                int  varIdx = -1;

                // remove spaces
                args     = args.Replace(" ", "");
                inString = inString.Replace(" ", "");

                // should we save a response as a variable?
                for (int i = 0; i < 10 && !asVar; i++)
                {
                    string txt = string.Format("arr{0}=", i);
                    asVar = inString.IndexOf(txt) == 0;
                    if (asVar)
                    {
                        varIdx = i;
                    }
                }

                string[] paramList = args.Split(',');
                if (paramList.Length > 1 && paramList.Length <= 5)
                {
                    // ecu_id, data (1..5)
                    int           ecu_id = 0;
                    int           dlc    = paramList.Length - 1;
                    List <string> sData  = new List <string>();
                    bool          failed = false;
                    object        calc   = null;

                    // ecalute: req id
                    calc   = canAnalyzer.MyCalc.evaluate(paramList[0]);
                    failed = calc == null;
                    if (!failed)
                    {
                        ecu_id = canAnalyzer.MyCalc.objToI32(calc);
                    }
                    if (!failed)
                    {
                        failed = ecu_id == 0 || ecu_id > 0xFF;
                    }

                    // evaluate: data bytes
                    for (int i = 1; i < paramList.Length && !failed; i++)
                    {
                        string param  = paramList[i];
                        string s_byte = null;
                        calc   = canAnalyzer.MyCalc.evaluate(param);
                        failed = calc == null;

                        // just append
                        if (!failed)
                        {
                            byte b = canAnalyzer.MyCalc.objToByte(calc);
                            s_byte = b.ToString();
                        }
                        else
                        {
                            string s_tmp = null;
                            // may contain variables
                            if (param.Contains("var"))
                            {
                                const string pattern = @"var\d\[\s*\d\s*\]";
                                s_tmp = Regex.Replace(param, pattern, "1", RegexOptions.Singleline);
                            }
                            // evaluate
                            if (s_tmp != null)
                            {
                                calc   = canAnalyzer.MyCalc.evaluate(s_tmp);
                                failed = calc == null;
                                // append
                                if (!failed)
                                {
                                    s_byte = param.ToString();
                                }
                            }
                        }

                        // append
                        if (!failed)
                        {
                            if (s_byte != null)
                            {
                                sData.Add(s_byte);
                            }
                            else
                            {
                                failed = true;
                            }
                        }
                    }

                    if (!failed)
                    {
                        cmd.cmd          = cmdType.udsReqSend;
                        cmd.paramsUdsReq = new cmdUdsReqParams((byte)(ecu_id), sData.ToArray(), asVar, varIdx);
                        cmd.error        = false;
                    }
                }
            }

            return(cmd);
        }
        // format:  printf("format", arg1, arg2, ..);
        // example: printf("rpm = %d", var[0] << 8 | var[1]);
        static private scriptParserCmd parseTrace(string funcName, string args)
        {
            // create a new cmd
            scriptParserCmd cmd = new scriptParserCmd();

            cmd.error = true;

            if (funcName == "printf")
            {
                cmd.cmd = cmdType.trace;
                string[] argList = new string[args.Length - 1];

                var regFormat = Regex.Match(args, "\".*\"");
                if (regFormat.Success)
                {
                    string s = args.Replace(regFormat.Value, "");
                    s = s.Replace(" ", "");
                    if (s.Length > 0 && s[0] == ',')
                    {
                        s = s.Remove(0, 1);
                    }

                    string format = regFormat.Value;

                    List <string> ls = new List <string>(s.Split(','));

                    for (int i = 0; i < ls.Count; i++)
                    {
                        if (string.IsNullOrEmpty(ls[i]))
                        {
                            ls.RemoveAt(i);
                        }
                    }

                    // %x, %02x == X, X2
                    // %d
                    // %f

                    // List<string> lsFormat = new List<string>();


                    // check skobochki
                    foreach (string str in ls)
                    {
                        int open = 0, close = 0;
                        open  = str.ToCharArray().Where(i => i == '(').Count();
                        close = str.ToCharArray().Where(i => i == ')').Count();
                        if (open != close)
                        {
                            return(cmd);
                        }
                    }

                    // extra format parser
                    // {s0-s6}
                    // {0-6}
                    var regFormatVarsAsArray = Regex.Matches(format, @"{(\d+)-(\d+)}");
                    foreach (Match v in regFormatVarsAsArray)
                    {
                        string s1 = v.Groups[1].ToString();
                        string s2 = v.Groups[2].ToString();
                        int    i1 = Convert.ToInt32(s1);
                        int    i2 = Convert.ToInt32(s2);

                        if (i2 > i1)
                        {
                            string newFormat = string.Empty;
                            for (int i = i1; i <= i2; i++)
                            {
                                newFormat += "{" + i.ToString() + "}";
                            }
                            format = format.Replace(v.Groups[0].ToString(), newFormat);
                        }
                    }

                    var regFormatVarsStrAsArray = Regex.Matches(format, @"{s(\d+)-s(\d+)}");
                    foreach (Match v in regFormatVarsStrAsArray)
                    {
                        string s1 = v.Groups[1].ToString();
                        string s2 = v.Groups[2].ToString();
                        int    i1 = Convert.ToInt32(s1);
                        int    i2 = Convert.ToInt32(s2);

                        if (i2 > i1)
                        {
                            string newFormat = string.Empty;
                            for (int i = i1; i <= i2; i++)
                            {
                                newFormat += "{s" + i.ToString() + "}";
                            }
                            format = format.Replace(v.Groups[0].ToString(), newFormat);
                        }
                    }

                    cmd.cmd         = cmdType.trace;
                    cmd.paramsTrace = new cmdTrace(format, ls.ToArray());
                    cmd.error       = false;
                }
            }


            return(cmd);
        }
        // format: read(0x316, 1000, **, 0x*F, 0x33, ***);
        //         var2 = read(0x123, ***);
        static private scriptParserCmd parseRead(string inString, string funcName, string args)
        {
            // create a new cmd
            scriptParserCmd cmd = new scriptParserCmd();

            cmd.error = true;

            if (funcName == "read" || funcName == "read11" || funcName == "read29")
            {
                string inString_original = inString;

                // remove spaces
                args     = args.Replace(" ", "");
                inString = inString.Replace(" ", "");

                bool is29bit = funcName.Contains("29");
                bool asVar   = false;
                int  varIdx  = -1;

                // should we save a response as a variable?
                for (int i = 0; i <= 9 && !asVar; i++)
                {
                    string txt = string.Format("var{0}=", i);
                    asVar = inString.IndexOf(txt) == 0;
                    if (asVar)
                    {
                        varIdx = i;
                    }
                }

                // parse all the arguments
                string[] paramList = args.Split(',');
                if (paramList.Length > 2 && paramList.Length <= 10)
                {
                    // id, tmo, data mask (1..8)
                    int      canID        = -1;
                    string   canIdPattern = string.Empty;
                    int      tmo          = -1;
                    string   mask         = args.Substring(paramList[0].Length + paramList[1].Length + 2); // +2 commas
                    string[] dataMask     = mask.Split(',');
                    bool     failed       = false;

                    // get CAN id
                    if (!parseReadId(paramList[0], is29bit, out canID, out canIdPattern))
                    {
                        canID        = -1;
                        canIdPattern = string.Empty;
                        failed       = true;
                    }
                    // timeout
                    tmo = parseReadTimeout(paramList[1]);
                    if (tmo <= 0)
                    {
                        failed = true;
                    }

                    int minDLC = -1;
                    for (int i = 0; i < dataMask.Length && -1 == minDLC; i++)
                    {
                        if (dataMask[i].Contains("***"))
                        {
                            minDLC = i;
                        }
                    }


                    // get a var name

                    /*
                     * var regVarName = Regex.Match(inString, @"var\s+(\w+)\s*=");
                     * string varName = string.Empty;
                     * if (regVarName.Success)
                     *  varName = regVarName.Groups[1].ToString();
                     */
                    if (!failed)
                    {
                        cmd.cmd        = cmdType.read;
                        cmd.paramsRead = new cmdReadParams(canID, dataMask, is29bit, tmo, minDLC, asVar, varIdx);
                        cmd.error      = false;
                    }
                }
            }

            return(cmd);
        }
        // parser: send a CAN message
        static private scriptParserCmd parseSend(string funcName, string args)
        {
            // create a new cmd
            scriptParserCmd cmd = new scriptParserCmd();

            cmd.error = true;

            // remove spaces
            args = args.Replace(" ", "");

            if (funcName == "send" || funcName == "send11" || funcName == "send29")
            {
                bool is29bit = funcName.Contains("29");

                string[] paramList = args.Split(',');
                if (paramList.Length >= 2 && paramList.Length <= 9)
                {
                    // id, data (1..8)
                    int      canID  = -1;
                    int      dlc    = paramList.Length - 1;
                    string[] sData  = new string[dlc];
                    bool     failed = false;
                    object   calc   = null;

                    // ecalute: can id
                    //int val = -1;
                    calc = canAnalyzer.MyCalc.evaluate(paramList[0]);

                    failed = (calc == null) ||
                             (Convert.ToInt32(calc) > canAnalyzer.canMessageId.GetMaxId(is29bit));

                    if (!failed)
                    {
                        canID = canAnalyzer.MyCalc.objToI32(calc);
                    }

                    // evaluate: data bytes
                    for (int i = 1; i < paramList.Length && !failed; i++)
                    {
                        string param  = paramList[i];
                        string s_byte = null;
                        calc   = canAnalyzer.MyCalc.evaluate(param);
                        failed = calc == null;

                        // just append
                        if (!failed)
                        {
                            byte b = canAnalyzer.MyCalc.objToByte(calc);
                            s_byte = b.ToString();
                        }
                        else
                        {
                            string s_tmp = null;
                            // may contain variables
                            if (param.Contains("var"))
                            {
                                const string pattern = @"var\d\[\s*\d\s*\]";
                                s_tmp = Regex.Replace(param, pattern, "1", RegexOptions.Singleline);
                            }
                            // may contain arrays
                            if (param.Contains("arr"))
                            {
                                const string pattern = @"arr\d\[\s*\d\s*\]";
                                s_tmp = Regex.Replace(param, pattern, "1", RegexOptions.Singleline);
                            }
                            // evaluate
                            if (s_tmp != null)
                            {
                                calc   = canAnalyzer.MyCalc.evaluate(s_tmp);
                                failed = calc == null;
                                // append
                                if (!failed)
                                {
                                    s_byte = param.ToString();
                                }
                            }
                        }

                        // append
                        if (!failed)
                        {
                            if (s_byte != null)
                            {
                                sData[i - 1] = s_byte;
                            }
                            else
                            {
                                failed = true;
                            }
                        }
                    }

                    if (!failed)
                    {
                        cmd.cmd        = cmdType.send;
                        cmd.paramsSend = new cmdSendParams(canID, sData, is29bit);
                        cmd.error      = false;
                    }
                }
            }

            return(cmd);
        }