<commit_before>"use strict";

import { spawn, ChildProcess } from 'child_process';
import { BreakpointEvent, DebugSession, Handles, InitializedEvent, Scope,
  Source as VSSource, StackFrame, StoppedEvent, Variable, OutputEvent,
  TerminatedEvent } from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import * as WebSocket from 'ws';

import { BreakpointData, Source as WDSource, Respond, SuspendEventMessage,
  InitialBreakpointsResponds, SourceMessage, IdMap, StoppedMessage,
  StackTraceResponse, StackTraceRequest, ScopesRequest, ScopesResponse,
  StepMessage, StepType, VariablesRequest, VariablesResponse, ThreadsResponse,
  createLineBreakpointData } from './messages';

export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
  /** Path to the main program */
  program: string;

  /** Arguments to the main program */
  args?: string[];

  /** Workding directory */
  cwd: string;

  /** Automatically stop after launch */
  stopOnEntry?: boolean;

  /** Path to the SOMns interpreter */
  runtime?: string;

  /** Optional interpreter arguments */
  runtimeArgs?: string[];
}

export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
  /** Debugger port of the interpreter */
  port: number
}

interface BreakpointPair {
  vs:  DebugProtocol.Breakpoint;
  som: BreakpointData;
}

class SomDebugSession extends DebugSession {
  private socket: WebSocket;
  private somProc: ChildProcess;

  private breakpoints: IdMap<IdMap<BreakpointPair>>;
  private nextBreakpointId: number;
  private bufferedBreakpoints: BreakpointPair[];
  private sources: IdMap<WDSource>;

  private nextRequestId: number;
  private requests: IdMap<any>;

  private varHandles : Handles<string>;

  public constructor() {
    super();

    this.socket = null;

    this.breakpoints = {};
    this.nextBreakpointId = 0;
    this.bufferedBreakpoints = [];

    this.sources = {};

    this.nextRequestId = 0;
    this.requests = {};

    this.varHandles = new Handles<string>();

    // Truffle source sections use 1-based indexes
    this.setDebuggerColumnsStartAt1(true);
    this.setDebuggerLinesStartAt1(true);
  }

  protected initializeRequest(response: DebugProtocol.InitializeResponse,
      args: DebugProtocol.InitializeRequestArguments): void {
    this.sendEvent(new InitializedEvent());

    response.body.supportsConfigurationDoneRequest = true;
    response.body.supportsEvaluateForHovers = true;
    response.body.supportsRestartFrame = true;
    response.body.supportsStepBack = false; // TODO: figure out how to support

    // TODO: https://github.com/Microsoft/vscode-debugadapter-node/pull/44
    // response.body.supportsStepInTargetsRequest

    this.sendResponse(response);
  }

  protected launchRequest(response: DebugProtocol.LaunchResponse,
      args: LaunchRequestArguments): void {
    const options = {cwd: args.cwd};
    let somArgs = ['-G', '-wd', args.program];
    if (args.runtimeArgs) {
      somArgs = args.runtimeArgs.concat(somArgs);
    }
    if (args.args) {
      somArgs = somArgs.concat(args.args);
    }
    const cmdStr = args.runtime + ' ' + somArgs.join(' ');
    this.sendEvent(new OutputEvent(`cmd: ${cmdStr}`, 'console'));

    this.somProc = spawn(args.runtime, somArgs, options);
    let connecting = false;

    this.somProc.stdout.on('data', (data) => {
      const str = data.toString();
      this.sendEvent(new OutputEvent(str, 'stdout'));
      if (str.includes("Started HTTP Server") && !connecting) {
        this.connectDebugger(response, 7977);
      }
    });
    this.somProc.stderr.on('data', (data) => {
      const str = data.toString();
      this.sendEvent(new OutputEvent(str, 'stderr'));
    });
    this.somProc.on('close', code => {
      this.sendEvent(new TerminatedEvent());
    })
  }

  protected attachRequest(response: DebugProtocol.AttachResponse,
      args: AttachRequestArguments): void {
    this.connectDebugger(response, args.port);
  }

  protected disconnectRequest(response: DebugProtocol.DisconnectResponse,
    args: DebugProtocol.DisconnectArguments): void {
    this.somProc.kill();
    this.sendResponse(response);
  }

  private connectDebugger(response: DebugProtocol.Response, port: number): void {
    this.socket = new WebSocket('ws://localhost:' + port);

    this.socket.on('open', () => {
      this.sendInitialBreakpoints();
    });

    this.socket.onmessage = this.onWDMessage.bind(this);

    this.sendResponse(response);
  }

  private onWDMessage(event): void {
    const data = JSON.parse(event.data);

    switch (data.type) {
      case "source":
        this.onSource(data);
        break;
      case "StackTraceResponse": {
        console.assert(this.requests[data.requestId]);
        const response = this.requests[data.requestId];
        delete this.requests[data.requestId];
        this.onProgramStackTraceResponse(data, response);
        break;
      }
      case "ScopesResponse": {
        console.assert(this.requests[data.requestId]);
        const response = this.requests[data.requestId];
        delete this.requests[data.requestId];
        this.onProgramScopesResponse(data, response);
        break;
      }
      case "VariablesResponse": {
        console.assert(this.requests[data.requestId]);
        const response = this.requests[data.requestId];
        delete this.requests[data.requestId];
        this.onProgramVariablesResponse(data, response);
      }
      case "StoppedEvent":
        this.onStoppedEvent(data);
        break;
      case "ThreadsResponse":
        const response = this.requests[data.requestId];
        delete this.requests[data.requestId];
        this.onThreadsResponse(data, response);
        break;
    }
  }

  private onSource(data: SourceMessage): void {
    for (let source of data.sources) {
      this.sources[source.uri] = source;
    }
  }

  private onStoppedEvent(data: StoppedMessage): void {
    this.sendEvent(new StoppedEvent(data.reason, data.activityId, data.text));
  }

  private sendInitialBreakpoints() : void {
    const updatedBreakpoints = [];
    const initialBreakpoints = [];

    for (const bpP of this.bufferedBreakpoints) {
      bpP.vs.verified = true;
      updatedBreakpoints.push(bpP.vs);
      initialBreakpoints.push(bpP.som);
    }
    this.bufferedBreakpoints.length = 0;
    const msg: InitialBreakpointsResponds = {
        action: "initialBreakpoints", breakpoints: initialBreakpoints,
        debuggerProtocol: true};
    this.send(msg);

    for (const vsBp of updatedBreakpoints) {
      this.sendEvent(new BreakpointEvent('Connection Established', vsBp));
    }
  }

  protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse,
      args: DebugProtocol.SetBreakpointsArguments): void {
    const connected = this.socket != null;
    const uri = 'file:' + args.source.path;
    const breakpoints = [];
    response.body = {breakpoints: breakpoints};

    // the `args` contain the latest list of line breakpoints for a uri
    // this means, we need to figure out which breakpoints were added, and which
    // were removed

    if (!this.breakpoints[uri]) { this.breakpoints[uri] = {}; }
    const currentBPs = this.breakpoints[uri];
    const removedBPs = Object.assign({}, currentBPs);

    for (let bp of args.breakpoints) {
      if (currentBPs[bp.line]) {
        // already exists, so, it is unchanged and not the removed list
        delete removedBPs[bp.line];
      } else {
        const lineBp = createLineBreakpointData(uri, bp.line, true);
        const vsBp = {
          id:       this.getNextBpId(),
          verified: connected,
          source:   args.source,
          line:     bp.line
        }
        const bpP = {vs: vsBp, som: lineBp};
        currentBPs[bp.line] = bpP;
        this.sendBreakpoint(currentBPs[bp.line], connected);
      }
    }

    // have not seen these breakpoint sin args.breakpoints, so they got disabled
    for (let line in removedBPs) {
      const bpP = removedBPs[line];
      bpP.som.enabled = false;
      this.sendBreakpoint(bpP, connected);
    }

    // that's the current list of bps, which we return to the IDE
    for (let line in currentBPs) {
      const bpP = currentBPs[line];
      breakpoints.push(bpP.vs);
    }
    this.sendResponse(response);
  }

  private getNextBpId(): number {
    const id = this.nextBreakpointId;
    this.nextBreakpointId += 1;
    return id;
  }

  private send(respond: Respond) {
    this.socket.send(JSON.stringify(respond));
  }

  private sendBreakpoint(bpP: BreakpointPair, connected: boolean): void {
    if (connected) {
      this.send({
        action: "updateBreakpoint",
        breakpoint: bpP.som
      });
    } else {
      this.bufferedBreakpoints.push(bpP);
    }<commit_msg>Ignore send until socket is initialized<commit_after>"use strict";

import { spawn, ChildProcess } from 'child_process';
import { BreakpointEvent, DebugSession, Handles, InitializedEvent, Scope,
  Source as VSSource, StackFrame, StoppedEvent, Variable, OutputEvent,
  TerminatedEvent } from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import * as WebSocket from 'ws';

import { BreakpointData, Source as WDSource, Respond, SuspendEventMessage,
  InitialBreakpointsResponds, SourceMessage, IdMap, StoppedMessage,
  StackTraceResponse, StackTraceRequest, ScopesRequest, ScopesResponse,
  StepMessage, StepType, VariablesRequest, VariablesResponse, ThreadsResponse,
  createLineBreakpointData } from './messages';

export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
  /** Path to the main program */
  program: string;

  /** Arguments to the main program */
  args?: string[];

  /** Workding directory */
  cwd: string;

  /** Automatically stop after launch */
  stopOnEntry?: boolean;

  /** Path to the SOMns interpreter */
  runtime?: string;

  /** Optional interpreter arguments */
  runtimeArgs?: string[];
}

export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments {
  /** Debugger port of the interpreter */
  port: number
}

interface BreakpointPair {
  vs:  DebugProtocol.Breakpoint;
  som: BreakpointData;
}

class SomDebugSession extends DebugSession {
  private socket: WebSocket;
  private somProc: ChildProcess;

  private breakpoints: IdMap<IdMap<BreakpointPair>>;
  private nextBreakpointId: number;
  private bufferedBreakpoints: BreakpointPair[];
  private sources: IdMap<WDSource>;

  private nextRequestId: number;
  private requests: IdMap<any>;

  private varHandles : Handles<string>;

  public constructor() {
    super();

    this.socket = null;

    this.breakpoints = {};
    this.nextBreakpointId = 0;
    this.bufferedBreakpoints = [];

    this.sources = {};

    this.nextRequestId = 0;
    this.requests = {};

    this.varHandles = new Handles<string>();

    // Truffle source sections use 1-based indexes
    this.setDebuggerColumnsStartAt1(true);
    this.setDebuggerLinesStartAt1(true);
  }

  protected initializeRequest(response: DebugProtocol.InitializeResponse,
      args: DebugProtocol.InitializeRequestArguments): void {
    this.sendEvent(new InitializedEvent());

    response.body.supportsConfigurationDoneRequest = true;
    response.body.supportsEvaluateForHovers = true;
    response.body.supportsRestartFrame = true;
    response.body.supportsStepBack = false; // TODO: figure out how to support

    // TODO: https://github.com/Microsoft/vscode-debugadapter-node/pull/44
    // response.body.supportsStepInTargetsRequest

    this.sendResponse(response);
  }

  protected launchRequest(response: DebugProtocol.LaunchResponse,
      args: LaunchRequestArguments): void {
    const options = {cwd: args.cwd};
    let somArgs = ['-G', '-wd', args.program];
    if (args.runtimeArgs) {
      somArgs = args.runtimeArgs.concat(somArgs);
    }
    if (args.args) {
      somArgs = somArgs.concat(args.args);
    }
    const cmdStr = args.runtime + ' ' + somArgs.join(' ');
    this.sendEvent(new OutputEvent(`cmd: ${cmdStr}`, 'console'));

    this.somProc = spawn(args.runtime, somArgs, options);
    let connecting = false;

    this.somProc.stdout.on('data', (data) => {
      const str = data.toString();
      this.sendEvent(new OutputEvent(str, 'stdout'));
      if (str.includes("Started HTTP Server") && !connecting) {
        this.connectDebugger(response, 7977);
      }
    });
    this.somProc.stderr.on('data', (data) => {
      const str = data.toString();
      this.sendEvent(new OutputEvent(str, 'stderr'));
    });
    this.somProc.on('close', code => {
      this.sendEvent(new TerminatedEvent());
    })
  }

  protected attachRequest(response: DebugProtocol.AttachResponse,
      args: AttachRequestArguments): void {
    this.connectDebugger(response, args.port);
  }

  protected disconnectRequest(response: DebugProtocol.DisconnectResponse,
    args: DebugProtocol.DisconnectArguments): void {
    this.somProc.kill();
    this.sendResponse(response);
  }

  private connectDebugger(response: DebugProtocol.Response, port: number): void {
    this.socket = new WebSocket('ws://localhost:' + port);

    this.socket.on('open', () => {
      this.sendInitialBreakpoints();
    });

    this.socket.onmessage = this.onWDMessage.bind(this);

    this.sendResponse(response);
  }

  private onWDMessage(event): void {
    const data = JSON.parse(event.data);

    switch (data.type) {
      case "source":
        this.onSource(data);
        break;
      case "StackTraceResponse": {
        console.assert(this.requests[data.requestId]);
        const response = this.requests[data.requestId];
        delete this.requests[data.requestId];
        this.onProgramStackTraceResponse(data, response);
        break;
      }
      case "ScopesResponse": {
        console.assert(this.requests[data.requestId]);
        const response = this.requests[data.requestId];
        delete this.requests[data.requestId];
        this.onProgramScopesResponse(data, response);
        break;
      }
      case "VariablesResponse": {
        console.assert(this.requests[data.requestId]);
        const response = this.requests[data.requestId];
        delete this.requests[data.requestId];
        this.onProgramVariablesResponse(data, response);
      }
      case "StoppedEvent":
        this.onStoppedEvent(data);
        break;
      case "ThreadsResponse":
        const response = this.requests[data.requestId];
        delete this.requests[data.requestId];
        this.onThreadsResponse(data, response);
        break;
    }
  }

  private onSource(data: SourceMessage): void {
    for (let source of data.sources) {
      this.sources[source.uri] = source;
    }
  }

  private onStoppedEvent(data: StoppedMessage): void {
    this.sendEvent(new StoppedEvent(data.reason, data.activityId, data.text));
  }

  private sendInitialBreakpoints() : void {
    const updatedBreakpoints = [];
    const initialBreakpoints = [];

    for (const bpP of this.bufferedBreakpoints) {
      bpP.vs.verified = true;
      updatedBreakpoints.push(bpP.vs);
      initialBreakpoints.push(bpP.som);
    }
    this.bufferedBreakpoints.length = 0;
    const msg: InitialBreakpointsResponds = {
        action: "initialBreakpoints", breakpoints: initialBreakpoints,
        debuggerProtocol: true};
    this.send(msg);

    for (const vsBp of updatedBreakpoints) {
      this.sendEvent(new BreakpointEvent('Connection Established', vsBp));
    }
  }

  protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse,
      args: DebugProtocol.SetBreakpointsArguments): void {
    const connected = this.socket != null;
    const uri = 'file:' + args.source.path;
    const breakpoints = [];
    response.body = {breakpoints: breakpoints};

    // the `args` contain the latest list of line breakpoints for a uri
    // this means, we need to figure out which breakpoints were added, and which
    // were removed

    if (!this.breakpoints[uri]) { this.breakpoints[uri] = {}; }
    const currentBPs = this.breakpoints[uri];
    const removedBPs = Object.assign({}, currentBPs);

    for (let bp of args.breakpoints) {
      if (currentBPs[bp.line]) {
        // already exists, so, it is unchanged and not the removed list
        delete removedBPs[bp.line];
      } else {
        const lineBp = createLineBreakpointData(uri, bp.line, true);
        const vsBp = {
          id:       this.getNextBpId(),
          verified: connected,
          source:   args.source,
          line:     bp.line
        }
        const bpP = {vs: vsBp, som: lineBp};
        currentBPs[bp.line] = bpP;
        this.sendBreakpoint(currentBPs[bp.line], connected);
      }
    }

    // have not seen these breakpoint sin args.breakpoints, so they got disabled
    for (let line in removedBPs) {
      const bpP = removedBPs[line];
      bpP.som.enabled = false;
      this.sendBreakpoint(bpP, connected);
    }

    // that's the current list of bps, which we return to the IDE
    for (let line in currentBPs) {
      const bpP = currentBPs[line];
      breakpoints.push(bpP.vs);
    }
    this.sendResponse(response);
  }

  private getNextBpId(): number {
    const id = this.nextBreakpointId;
    this.nextBreakpointId += 1;
    return id;
  }

  private send(respond: Respond) {
    if (this.socket) {
      this.socket.send(JSON.stringify(respond));
    }
  }

  private sendBreakpoint(bpP: BreakpointPair, connected: boolean): void {
    if (connected) {
      this.send({
        action: "updateBreakpoint",
        breakpoint: bpP.som
      });
    } else {
      this.bufferedBreakpoints.push(bpP);
    }
{'fraction_non_alphanumeric': 0.11457212436993827, 'fraction_numerical': 0.001472503822846463, 'mean_word_length': 3.264187394349191, 'pattern_counts': {'":': 12, '<': 15, '<?xml version=': 0, '>': 23, 'https://': 2, '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': '2045370', 'n_tokens_mistral': 5212, 'n_tokens_neox': 5058, 'n_words': 1169}