private static string CalculatePcRelativeAddress(string instruction, int pcAddress, InstructionPart part, ushort bits)
        {
            var mult    = GrabBits(part.Code, 8, 8);
            var add     = GrabBits(part.Code, 0, 8);
            var numeric = (short)bits;

            numeric <<= 16 - part.Length;
            numeric >>= 16 - part.Length; // get all the extra bits set to one so that the numeric value is correct
            if (instruction.Contains("#"))
            {
                // this is the first # in this instruction.
                var address = pcAddress - (pcAddress % mult) + numeric * mult + add;
                var end     = instruction.EndsWith("]") ? "]" : string.Empty;
                instruction = instruction.Split("#=")[0] + "#" + end;
                instruction = instruction.Replace("#", $"<{address:X6}>");
            }
            else
            {
                // this is an additional # in the same instruction.
                // decode back from the old one
                var address = int.Parse(instruction.Split('<')[1].Split('>')[0], NumberStyles.HexNumber);
                address -= pcAddress - (pcAddress % mult) + add;
                address /= mult;
                address  = (address & ((1 << part.Length) - 1)); // drop the high bits, keep only the data bits. This makes it lose the sign.
                // concat the new numeric
                address += bits << part.Length;                  // the new numeric is the higher bits
                // shift to get arithmetic sign bits
                address <<= 32 - part.Length * 2;
                address >>= 32 - part.Length * 2; // since address is a signed int, C# right-shift will carry 1-bits down if the high bit is set. This is what we want to happen.
                // encode again
                address    *= mult;
                address    += pcAddress - (pcAddress % mult) + add;
                instruction = instruction.Split('<')[0] + $"<{address:X6}>" + (instruction + " ").Split('>')[1].Trim(); // extra space / trim let's us get everything after the '>', even if it's empty
            }

            return(instruction);
        }
        private Instruction(string compiled, string script)
        {
            var parts = compiled.ToLower().Split(' ');

            foreach (var part in parts)
            {
                if (part.StartsWith("0") || part.StartsWith("1"))
                {
                    var code = ToBits(part);
                    instructionParts.Add(new InstructionPart(InstructionArgType.OpCode, code, part.Length));
                }
                else if (part.StartsWith("r"))
                {
                    instructionParts.Add(new InstructionPart(InstructionArgType.Register, 0, 3, part));
                }
                else if (part.StartsWith("#"))
                {
                    ushort code = 0;
                    if (script.Contains("#=pc+#*"))
                    {
                        var encoding = script.Split("#=pc+#*")[1].Split('+');
                        code   = byte.TryParse(encoding[0], out var mult) ? mult : default;
                        code <<= 8;
                        code  |= byte.TryParse(encoding[1].Trim(']'), out var add) ? add : default;
                    }
                    int length = 0;
                    if (part.Length > 1)
                    {
                        int.TryParse(part.Substring(1), out length);
                    }
                    instructionParts.Add(new InstructionPart(InstructionArgType.Numeric, code, length));
                }
                else if (part == "h")
                {
                    instructionParts.Add(new InstructionPart(InstructionArgType.HighRegister, 0, 1));
                }
                else if (part == "list")
                {
                    instructionParts.Add(new InstructionPart(InstructionArgType.List, 0, 8));
                }
                else if (part == "tsil")
                {
                    instructionParts.Add(new InstructionPart(InstructionArgType.ReverseList, 0, 8));
                }
                else if (part == "cond")
                {
                    instructionParts.Add(new InstructionPart(InstructionArgType.Condition, 0, 4));
                }
            }

            var totalLength = instructionParts.Sum(part => part.Length);

            if (totalLength > 16)
            {
                ByteLength = totalLength / 8;
            }
            var remainingLength = ByteLength * 8 - totalLength;

            for (int i = 0; i < instructionParts.Count && remainingLength > 0; i++)
            {
                if (instructionParts[i].Type != InstructionArgType.Numeric)
                {
                    continue;
                }
                instructionParts[i] = new InstructionPart(InstructionArgType.Numeric, instructionParts[i].Code, remainingLength);
                totalLength        += remainingLength;
            }

            if (totalLength % 16 != 0)
            {
                throw new ArgumentException($"There were {totalLength} bits in the command, but commands must be a multiple of 16 bits long!");
            }

            template = script.ToLower();
        }