===== ISTEM =====
Solve the following programming task.

TASK: TIK Register Machine

Write a function 'runTik(program)' that runs a program written in TIK, a small made-up language, and returns an array of the numbers it outputs.

Program text:
- Instructions are separated by newlines. Leading and trailing spaces on a line are ignored.
- Lines that are empty (or only spaces) and lines whose first non-space character is '#' are ignored and are not instructions.
- Within an instruction, tokens are separated by one or more spaces.

Machine:
- There are four registers named a, b, c and d. All start at 0.
- Every value stored in a register is reduced to the range 0..999 by taking it modulo 1000, always giving a non-negative result (for example -1 becomes 999 and 1003 becomes 3).
- An integer literal is an optional '-' followed by one or more digits ('+5' and '5.0' are not valid).
- An operand written as X below is either a register name or an integer literal. Its value is the register's value, or the literal reduced to 0..999 in the same way.

Instructions (R must be a register name, N must be an integer literal):
- set R X: R becomes the value of X.
- add R X: R becomes R plus the value of X.
- mul R X: R becomes R times the value of X.
- out X: appends the value of X to the output.
- jmp X N: if the value of X is not 0, the next instruction is the one N instructions away from this one (N may be negative; ignored lines are not counted). Otherwise execution continues with the next instruction.
Instruction names and register names are lowercase; anything else is invalid.

Running:
- Before running anything, check the whole program. If any line that is not ignored is not a valid instruction (unknown name, wrong number of operands, or an invalid register, literal or operand), throw an Error whose message is 'bad instruction at line K', where K is the 1-based line number of the first such line in the original text, counting ignored lines too. In that case nothing is run.
- Execution starts at the first instruction and stops normally when the next instruction position is before the first instruction or after the last one.
- A program may execute at most 10,000 instructions. If it has not stopped after executing 10,000 instructions, throw an Error with the message 'step limit'.

STARTER CODE (javascript):
function runTik(program) {
  // Write your code here
}

module.exports = { runTik };


RULES:
- Keep the function name and signature EXACTLY as given.
- Return working code only. No explanations.
- Put the code in a single ``` block.
- Try to solve it first. If you genuinely cannot, write only this single
  line instead of producing faulty code: CANNOT_SOLVE

===== HAM YANIT =====
```javascript
function runTik(program) {
  const instructions = [];
  const registers = [0, 0, 0, 0];
  const output = [];

  const isRegister = token => /^[abcd]$/.test(token);
  const isInteger = token => /^-?[0-9]+$/.test(token);
  const isOperand = token => isRegister(token) || isInteger(token);
  const registerIndex = token => token.charCodeAt(0) - 97;

  function parseOperand(token) {
    if (isRegister(token)) {
      return { register: registerIndex(token) };
    }
    const value = BigInt(token);
    return { value: Number(((value % 1000n) + 1000n) % 1000n) };
  }

  function valueOf(operand) {
    return operand.register === undefined
      ? operand.value
      : registers[operand.register];
  }

  const lines = program.split("\n");

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i].trim();
    if (line === "" || line.startsWith("#")) continue;

    const tokens = line.split(/ +/);
    const [op, first, second] = tokens;
    let instruction;

    if (
      (op === "set" || op === "add" || op === "mul") &&
      tokens.length === 3 &&
      isRegister(first) &&
      isOperand(second)
    ) {
      instruction = {
        op,
        target: registerIndex(first),
        operand: parseOperand(second)
      };
    } else if (
      op === "out" &&
      tokens.length === 2 &&
      isOperand(first)
    ) {
      instruction = { op, operand: parseOperand(first) };
    } else if (
      op === "jmp" &&
      tokens.length === 3 &&
      isOperand(first) &&
      isInteger(second)
    ) {
      instruction = {
        op,
        operand: parseOperand(first),
        offset: BigInt(second)
      };
    } else {
      throw new Error(`bad instruction at line ${i + 1}`);
    }

    instructions.push(instruction);
  }

  let pc = 0;
  let steps = 0;
  const instructionCount = BigInt(instructions.length);

  while (pc >= 0 && pc < instructions.length) {
    if (steps === 10000) throw new Error("step limit");
    steps++;

    const instruction = instructions[pc];
    const value = valueOf(instruction.operand);

    switch (instruction.op) {
      case "set":
        registers[instruction.target] = value;
        break;
      case "add":
        registers[instruction.target] =
          (registers[instruction.target] + value) % 1000;
        break;
      case "mul":
        registers[instruction.target] =
          (registers[instruction.target] * value) % 1000;
        break;
      case "out":
        output.push(value);
        break;
      case "jmp":
        if (value !== 0) {
          const next = BigInt(pc) + instruction.offset;
          if (next < 0n || next >= instructionCount) return output;
          pc = Number(next);
          continue;
        }
        break;
    }

    pc++;
  }

  return output;
}

module.exports = { runTik };
```