<commit_before>import { DebugTrace, SlotInfo } from '../debug-trace/debug-trace';

// The TraceOp enum must stay in sync with SkSL::SkVMTraceInfo::Op.
enum TraceOp {
  Line  = 0,
  Var   = 1,
  Enter = 2,
  Exit  = 3,
  Scope = 4,
}

// The NumberKind enum must stay in sync with SkSL::Type::NumberKind.
enum NumberKind {
  Float      = 0,
  Signed     = 1,
  Unsigned   = 2,
  Boolean    = 3,
  Nonnumeric = 4,
}

// Trace data comes in from the JSON as a number[]. We unpack it into a TraceInfo for ease of use.
type TraceInfo = {
  op: TraceOp;
  data: number[];
};

type StackFrame = {
  // A FunctionInfo from trace.functions.
  func: number;
  // The current line number within the function.
  line: number;
  // Any variable slots which have been touched in this function.
  displayMask: boolean[];
};

type Slot = {
  // The current raw value held in this slot (as a 32-bit integer, not bit-punned).
  value: number;
  // The scope depth associated with this slot (as indicated by trace_scope).
  scope: number;
  // When was the variable in this slot most recently written? (as a cursor position)
  writeTime: number;
};

export type VariableData = {
  // A SlotInfo from trace.slots.
  slotIndex: number;
  // Has this slot been written-to since the last step call?
  dirty: boolean;
  // The current value held in this slot (properly bit-punned/cast to the expected type)
  value: number | boolean;
};

export class DebugTracePlayer {
  private trace: DebugTrace | null = null;

  // The position of the read head within the trace array.
  private cursor: number = 0;

  // Tracks the current scope depth (as indicated by trace_scope).
  private scope: number = 0;

  // Tracks assignments into our data slots.
  private slots: Slot[] = [];

  // Tracks the trace stack (as indicated by trace_enter and trace_exit).
  private stack: StackFrame[] = []; // the execution stack

  // Tracks which line numbers are reached by the trace, and the number of times it's reached.
  private lineNumbers: Map<number, number> = new Map();

  // Tracks all the data slots which have been touched during the current step.
  private dirtyMask: boolean[] = [];

  // Tracks all the data slots which hold function return values.
  private returnValues: boolean[] = [];

  // Tracks line numbers that have breakpoints set on them.
  private breakpointLines: Set<number> = new Set();

  /** Throws an error if a precondition is not met. Indicates a logic bug or invalid trace. */
  private check(result: boolean): void {
    if (!result) {
      throw new Error('check failed');
    }
  }

  /** Copies trace info from the JSON number array into a TraceInfo struct. */
  private getTraceInfo(position: number): TraceInfo {
    this.check(position < this.trace!.trace.length);
    this.check(this.trace!.trace[position][0] in TraceOp);

    const info: TraceInfo = {
      op: this.trace!.trace[position][0] as TraceOp,
      data: this.trace!.trace[position].slice(1),
    };
    return info;
  }

  /** Resets playback to the start of the trace. Breakpoints are not cleared. */
  public reset(trace: DebugTrace | null): void {
    const nslots = trace?.slots?.length ?? 0;

    const globalStackFrame: StackFrame = {
      func: -1,
      line: -1,
      displayMask: Array<boolean>(nslots).map(() => (false)),
    };

    this.trace = trace;
    this.cursor = 0;
    this.slots = [];
    this.stack = [globalStackFrame];
    this.dirtyMask = Array<boolean>(nslots).map(() => (false));
    this.returnValues = Array<boolean>(nslots).map(() => (false));

    if (trace !== null) {
      this.slots = trace.slots.map((): Slot => ({
        value: 0,
        scope: Infinity,
        writeTime: 0,
      }));
      this.returnValues = trace.slots.map((slotInfo: SlotInfo): boolean =>
        (slotInfo.retval ?? -1) >= 0
      );

      // Build a map holding the number of times each line is reached.
      this.lineNumbers.clear();
      trace.trace.forEach((_, traceIdx: number) => {
        const info: TraceInfo = this.getTraceInfo(traceIdx);
        if (info.op === TraceOp.Line) {
          const lineNumber = info.data[0];
          const lineCount = this.lineNumbers.get(lineNumber) ?? 0;
          this.lineNumbers.set(lineNumber, lineCount + 1);
        }
      });
    }
  }

  /** Advances the simulation to the next Line op. */
  public step(): void {
    this.tidyState();
    while (!this.traceHasCompleted()) {
      if (this.execute(this.cursor++)) {
        break;
      }
    }
  }

  /**
   * Advances the simulation to the next Line op, skipping past matched Enter/Exit pairs.
   * Breakpoints will also stop the simulation even if we haven't reached an Exit.
   */
  public stepOver(): void {
    this.tidyState();
    const initialStackDepth = this.stack.length;

    while (!this.traceHasCompleted()) {
      const canEscapeFromThisStackDepth = (this.stack.length <= initialStackDepth);
      if (this.execute(this.cursor++)) {
        if (canEscapeFromThisStackDepth || this.atBreakpoint()) {
          break;
        }
      }
    }
  }

  public stepOut() : void {
    this.tidyState();
    const initialStackDepth = this.stack.length;

    while (!this.traceHasCompleted()) {
      if (this.execute(this.cursor++)) {
        const hasEscapedFromInitialStackDepth = (this.stack.length < initialStackDepth);
        if (hasEscapedFromInitialStackDepth || this.atBreakpoint()) {
          break;
        }
      }
    }
  }

  public run() : void {
    this.tidyState();

    while (!this.traceHasCompleted()) {<commit_msg>Add missing doc comments.<commit_after>import { DebugTrace, SlotInfo } from '../debug-trace/debug-trace';

// The TraceOp enum must stay in sync with SkSL::SkVMTraceInfo::Op.
enum TraceOp {
  Line  = 0,
  Var   = 1,
  Enter = 2,
  Exit  = 3,
  Scope = 4,
}

// The NumberKind enum must stay in sync with SkSL::Type::NumberKind.
enum NumberKind {
  Float      = 0,
  Signed     = 1,
  Unsigned   = 2,
  Boolean    = 3,
  Nonnumeric = 4,
}

// Trace data comes in from the JSON as a number[]. We unpack it into a TraceInfo for ease of use.
type TraceInfo = {
  op: TraceOp;
  data: number[];
};

type StackFrame = {
  // A FunctionInfo from trace.functions.
  func: number;
  // The current line number within the function.
  line: number;
  // Any variable slots which have been touched in this function.
  displayMask: boolean[];
};

type Slot = {
  // The current raw value held in this slot (as a 32-bit integer, not bit-punned).
  value: number;
  // The scope depth associated with this slot (as indicated by trace_scope).
  scope: number;
  // When was the variable in this slot most recently written? (as a cursor position)
  writeTime: number;
};

export type VariableData = {
  // A SlotInfo from trace.slots.
  slotIndex: number;
  // Has this slot been written-to since the last step call?
  dirty: boolean;
  // The current value held in this slot (properly bit-punned/cast to the expected type)
  value: number | boolean;
};

export class DebugTracePlayer {
  private trace: DebugTrace | null = null;

  // The position of the read head within the trace array.
  private cursor: number = 0;

  // Tracks the current scope depth (as indicated by trace_scope).
  private scope: number = 0;

  // Tracks assignments into our data slots.
  private slots: Slot[] = [];

  // Tracks the trace stack (as indicated by trace_enter and trace_exit).
  private stack: StackFrame[] = []; // the execution stack

  // Tracks which line numbers are reached by the trace, and the number of times it's reached.
  private lineNumbers: Map<number, number> = new Map();

  // Tracks all the data slots which have been touched during the current step.
  private dirtyMask: boolean[] = [];

  // Tracks all the data slots which hold function return values.
  private returnValues: boolean[] = [];

  // Tracks line numbers that have breakpoints set on them.
  private breakpointLines: Set<number> = new Set();

  /** Throws an error if a precondition is not met. Indicates a logic bug or invalid trace. */
  private check(result: boolean): void {
    if (!result) {
      throw new Error('check failed');
    }
  }

  /** Copies trace info from the JSON number array into a TraceInfo struct. */
  private getTraceInfo(position: number): TraceInfo {
    this.check(position < this.trace!.trace.length);
    this.check(this.trace!.trace[position][0] in TraceOp);

    const info: TraceInfo = {
      op: this.trace!.trace[position][0] as TraceOp,
      data: this.trace!.trace[position].slice(1),
    };
    return info;
  }

  /** Resets playback to the start of the trace. Breakpoints are not cleared. */
  public reset(trace: DebugTrace | null): void {
    const nslots = trace?.slots?.length ?? 0;

    const globalStackFrame: StackFrame = {
      func: -1,
      line: -1,
      displayMask: Array<boolean>(nslots).map(() => (false)),
    };

    this.trace = trace;
    this.cursor = 0;
    this.slots = [];
    this.stack = [globalStackFrame];
    this.dirtyMask = Array<boolean>(nslots).map(() => (false));
    this.returnValues = Array<boolean>(nslots).map(() => (false));

    if (trace !== null) {
      this.slots = trace.slots.map((): Slot => ({
        value: 0,
        scope: Infinity,
        writeTime: 0,
      }));
      this.returnValues = trace.slots.map((slotInfo: SlotInfo): boolean =>
        (slotInfo.retval ?? -1) >= 0
      );

      // Build a map holding the number of times each line is reached.
      this.lineNumbers.clear();
      trace.trace.forEach((_, traceIdx: number) => {
        const info: TraceInfo = this.getTraceInfo(traceIdx);
        if (info.op === TraceOp.Line) {
          const lineNumber = info.data[0];
          const lineCount = this.lineNumbers.get(lineNumber) ?? 0;
          this.lineNumbers.set(lineNumber, lineCount + 1);
        }
      });
    }
  }

  /** Advances the simulation to the next Line op. */
  public step(): void {
    this.tidyState();
    while (!this.traceHasCompleted()) {
      if (this.execute(this.cursor++)) {
        break;
      }
    }
  }

  /**
   * Advances the simulation to the next Line op, skipping past matched Enter/Exit pairs.
   * Breakpoints will also stop the simulation even if we haven't reached an Exit.
   */
  public stepOver(): void {
    this.tidyState();
    const initialStackDepth = this.stack.length;

    while (!this.traceHasCompleted()) {
      const canEscapeFromThisStackDepth = (this.stack.length <= initialStackDepth);
      if (this.execute(this.cursor++)) {
        if (canEscapeFromThisStackDepth || this.atBreakpoint()) {
          break;
        }
      }
    }
  }

  /**
   * Advances the simulation until we exit from the current stack frame.
   * Breakpoints will also stop the simulation even if we haven't left the stack frame.
   */
  public stepOut() : void {
    this.tidyState();
    const initialStackDepth = this.stack.length;

    while (!this.traceHasCompleted()) {
      if (this.execute(this.cursor++)) {
        const hasEscapedFromInitialStackDepth = (this.stack.length < initialStackDepth);
        if (hasEscapedFromInitialStackDepth || this.atBreakpoint()) {
          break;
        }
      }
    }
  }

  /** Advances the simulation until we hit a breakpoint, or the trace completes. */
  public run() : void {
    this.tidyState();

    while (!this.traceHasCompleted()) {
{'fraction_non_alphanumeric': 0.11967921036397286, 'fraction_numerical': 0.004935225169648365, 'mean_word_length': 2.7751164337990684, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7555222', 'n_tokens_mistral': 3448, 'n_tokens_neox': 3323, 'n_words': 1144}