internal void Parse()
        {
            CodeLine.LineStr = DefineTable.Dealias(CodeLine.LineStr);

            // Perform regex:
            const string instrPattern    = "glowramp";
            const string gap             = @"[\s|\t]*";
            const string ledsPattern     = @"\[(.*?)]";
            const string colorPattern    = @"\((.*?)\)";
            const string timePattern     = @"(\d+)";
            const string timeUnitPattern = @"(ms|s|m|h|t)";
            var          pattern         = $@"^{instrPattern}{gap}:{gap}{ledsPattern}{gap}{colorPattern}{gap}to{gap}{colorPattern}{gap}in{gap}{timePattern}{timeUnitPattern}$";
            var          match           = Regex.Match(CodeLine.LineStr, pattern);

            if (!match.Success)
            {
                Console.WriteLine("Invalid 'glowRamp' instruction");
                throw new Exception();
            }

            // Get zero-based led list:
            try
            {
                LedIdxList = match.Groups[1].Value.GetLeds();
            }
            catch
            {
                Console.WriteLine("Invalid LED(s) in 'glowRamp' instruction");
                throw new Exception();
            }

            // Get color:
            try
            {
                // Get start color:
                LedColorFrom = match.Groups[2].Value.GetColor();

                // Get end color:
                LedColorTo = match.Groups[3].Value.GetColor();
            }
            catch
            {
                Console.WriteLine("Invalid color in 'glowRamp' instruction");
                throw new Exception();
            }

            // Get ramp duration in millisecs:
            long millisecs = match.Groups[4].Value.GetDuration(match.Groups[5].Value);

            if (millisecs > 3888000000)    // 45-day upper limit.
            {
                Console.WriteLine("Pause duration exceed 45-days (3888000-seconds)");
                throw new Exception();
            }
            RampMs = (uint)millisecs;

            // Calculate from-to delta (negative values indicate decrement):
            LedColorDiff.Red    = LedColorTo.Red - LedColorFrom.Red;
            LedColorDiff.Green  = LedColorTo.Green - LedColorFrom.Green;
            LedColorDiff.Blue   = LedColorTo.Blue - LedColorFrom.Blue;
            LedColorDiff.Bright = LedColorTo.Bright - LedColorFrom.Bright;

            // Calculate red tick/color step to get as close as possible to target color in ramp ticks:
            if (LedColorDiff.Red != 0 && RampTicks >= Math.Abs(LedColorDiff.Red))
            {
                TickStep.Red  = (int)Math.Floor((double)RampTicks / Math.Abs(LedColorDiff.Red));
                ColorStep.Red = 1;
            }
            else if (LedColorDiff.Red != 0 && RampTicks < Math.Abs(LedColorDiff.Red))
            {
                TickStep.Red  = 1;
                ColorStep.Red = (int)Math.Floor((double)Math.Abs(LedColorDiff.Red) / RampTicks);
            }

            // Calculate green tick/color step to get as close as possible to target color in ramp ticks:
            if (LedColorDiff.Green != 0 && RampTicks >= Math.Abs(LedColorDiff.Green))
            {
                TickStep.Green  = (int)Math.Floor((double)RampTicks / Math.Abs(LedColorDiff.Green));
                ColorStep.Green = 1;
            }
            else if (LedColorDiff.Green != 0 && RampTicks < Math.Abs(LedColorDiff.Green))
            {
                TickStep.Green  = 1;
                ColorStep.Green = (int)Math.Floor((double)Math.Abs(LedColorDiff.Green) / RampTicks);
            }

            // Calculate blue tick/color step to get as close as possible to target color in ramp ticks:
            if (LedColorDiff.Blue != 0 && RampTicks >= Math.Abs(LedColorDiff.Blue))
            {
                TickStep.Blue  = (int)Math.Floor((double)RampTicks / Math.Abs(LedColorDiff.Blue));
                ColorStep.Blue = 1;
            }
            else if (LedColorDiff.Blue != 0 && RampTicks < Math.Abs(LedColorDiff.Blue))
            {
                TickStep.Blue  = 1;
                ColorStep.Blue = (int)Math.Floor((double)Math.Abs(LedColorDiff.Blue) / RampTicks);
            }

            // Calculate bright tick/color step to get as close as possible to target color in ramp ticks:
            if (LedColorDiff.Bright != 0 && RampTicks >= Math.Abs(LedColorDiff.Bright))
            {
                TickStep.Bright  = (int)Math.Floor((double)RampTicks / Math.Abs(LedColorDiff.Bright));
                ColorStep.Bright = 1;
            }
            else if (LedColorDiff.Bright != 0 && RampTicks < Math.Abs(LedColorDiff.Bright))
            {
                TickStep.Bright  = 1;
                ColorStep.Bright = (int)Math.Floor((double)Math.Abs(LedColorDiff.Bright) / RampTicks);
            }

            // Generate pre glow immediate instruction to initialize glow ramp:
            PreGlowImmediateInstruction = new GlowImmediateInstruction
            {
                LedIdxList = new List <int>(LedIdxList),
                LedColor   = LedColorFrom.DeepCopy(),
                Path       = Path,
                ZOrder     = ZOrder
            };

            // Generate post glow immediate instruction to finalize glow ramp:
            PostGlowImmediateInstruction = new GlowImmediateInstruction
            {
                LedIdxList = new List <int>(LedIdxList),
                LedColor   = LedColorTo.DeepCopy(),
                Path       = Path,
                ZOrder     = ZOrder
            };

            // Generate post-ramp ledstrip state:
            foreach (var ledIdx in LedIdxList)
            {
                LedstripPostState.LedStateList[ledIdx] = LedColorTo;
            }
        }
 internal bool Equals(GlowImmediateInstruction mi)
 {
     return(LedIdxList.OrderBy(x => x).SequenceEqual(mi.LedIdxList.OrderBy(x => x)) && LedColor.Equals(mi.LedColor));
 }
        internal static List <Instruction> ConvertToInstructions(List <CodeLine> codeLines, int path, int zOrder)
        {
            var instrSet   = new List <Instruction>();
            var funcGuid   = Guid.NewGuid();
            var currLineNb = 1;

            try
            {
                foreach (var codeLine in codeLines)
                {
                    currLineNb = codeLine.LineNb;

                    if (codeLine.LineStr.IsGlowImmediateInstruction())
                    {
                        var instr = new GlowImmediateInstruction(codeLine, path, zOrder);
                        instrSet.Add(instr);
                    }
                    else if (codeLine.LineStr.IsGlowRampInstruction())
                    {
                        var instr = new GlowRampInstruction(codeLine, path, zOrder);
                        instrSet.Add(instr.PreGlowImmediateInstruction);
                        instrSet.Add(instr);
                        instrSet.Add(instr.PostGlowImmediateInstruction);
                    }
                    else if (codeLine.LineStr.IsCallInstruction(out var repeatCount))
                    {
                        while (repeatCount-- > 0)
                        {
                            var instr = new CallInstruction(codeLine, path, zOrder);
                            instrSet.AddRange(instr.InstrList);
                        }
                    }
                    else if (codeLine.LineStr.IsCallAsyncInstruction(zOrder, out int newZOrder))
                    {
                        var nextExecutionPathIndex  = ++ExecutionPathIndex;
                        var pathActivateInstruction = new PathActivateInstruction(path, zOrder)   // use prev zOrder as this instruction is not in new path.
                        {
                            TargetPathIdx = (uint)nextExecutionPathIndex
                        };
                        var callInstruction    = new CallInstruction(codeLine, nextExecutionPathIndex, newZOrder);
                        var pathEndInstruction = new PathEndInstruction(nextExecutionPathIndex, newZOrder);
                        instrSet.Add(pathActivateInstruction);
                        instrSet.AddRange(callInstruction.InstrList);
                        instrSet.Add(pathEndInstruction);
                    }
                    else if (codeLine.LineStr.IsPauseInstruction())
                    {
                        var instr = new PauseInstruction(codeLine, path, zOrder);
                        instrSet.Add(instr);
                    }
                    else if (codeLine.LineStr.IsHereInstruction())
                    {
                        var instr = new HereInstruction(codeLine, path, zOrder, funcGuid);
                        instrSet.Add(instr);
                    }
                    else if (codeLine.LineStr.IsGotoInstruction())
                    {
                        var instr = new GotoInstruction(codeLine, path, zOrder, funcGuid);
                        instrSet.Add(instr);
                    }
                    else
                    {
                        Console.WriteLine($"Unknown instruction");
                        throw new Exception();
                    }
                }
            }
            catch
            {
                if (recursiveTip)
                {
                    recursiveTip = false;
                    Console.Write($"Error parsing line {currLineNb}");
                }
                throw new Exception();
            }

            return(instrSet);
        }