{
    Copyright (c) 2015 by Nikolay Nikolov

    This unit implements a class, which launches gdb in GDB/MI mode
    and allows sending textual commands to it and receiving the response

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}

unit GDBMIProc;

{$MODE objfpc}{$H+}

{$I globdir.inc}

interface

uses
  SysUtils, Classes, Process;

type
  TGDBProcess = class
  private
    FProcess: TProcess;
    FDebugLog: TextFile;

    function IsAlive: Boolean;
    procedure GDBWrite(const S: string);
    procedure DebugLn(const S: string);
    procedure DebugErrorLn(const S: string);
  public
    constructor Create;
    destructor Destroy; override;
    function GDBReadLn: string;
    procedure GDBWriteLn(const S: string);
    property Alive: Boolean read IsAlive;
  end;

var
  GdbProgramName: string = 'gdb';

implementation

uses
  fputils;

var
  DebugLogEnabled: Boolean = False;

function TGDBProcess.IsAlive: Boolean;
begin
  Result := Assigned(FProcess) and FProcess.Running;
end;

function TGDBProcess.GDBReadLn: string;
var
  C: Char;
begin
  Result := '';
  while FProcess.Running do
  begin
    FProcess.Output.Read(C, 1);
{$ifdef windows}
    { On windows we expect both #13#10 and #10 }
    if C = #13 then
    begin
      FProcess.Output.Read(C, 1);
      if C <> #10 then
        { #13 not followed by #10, what should we do? }
        Result := Result + #13;
    end;
{$endif windows}
    if C = #10 then
    begin
      DebugLn(Result);
      exit;
    end;
    Result := Result + C;
  end;
end;

constructor TGDBProcess.Create;
begin
  if DebugLogEnabled then
  begin
    AssignFile(FDebugLog, 'gdblog.txt');
    Rewrite(FDebugLog);
    CloseFile(FDebugLog);
  end;
  FProcess := TProcess.Create(nil);
  FProcess.Options := [poUsePipes, poStdErrToOutput];
  if (ExeExt<>'') and (pos(ExeExt,LowerCaseStr(GdbProgramName))=0) then
    FProcess.Executable := GdbProgramName+ExeExt
  else
    FProcess.Executable := GdbProgramName;
  FProcess.Parameters.Add('--interpreter=mi');
  try
    FProcess.Execute;
  except
    on e: Exception do
    begin
      DebugErrorLn('Could not start GDB: ' + e.Message);
      FreeAndNil(FProcess);
    end;
  end;
end;

destructor TGDBProcess.Destroy;
begin
  FProcess.Free;
  inherited Destroy;
end;

procedure TGDBProcess.DebugLn(const S: string);
begin
  if DebugLogEnabled then
  begin
    Append(FDebugLog);
    Writeln(FDebugLog, S);
    CloseFile(FDebugLog);
  end;
end;

procedure TGDBProcess.DebugErrorLn(const S: string);
begin
  DebugLn('ERROR: ' + S);
end;

procedure TGDBProcess.GDBWrite(const S: string);
begin
  FProcess.Input.Write(S[1], Length(S));
end;

procedure TGDBProcess.GDBWriteln(const S: string);
begin
  if not IsAlive then
  begin
    DebugErrorLn('Trying to send command to a dead GDB: ' + S);
    exit;
  end;
  DebugLn(S);
  GDBWrite(S + #10);
end;

begin
  if GetEnvironmentVariable('FPIDE_GDBLOG') = '1' then
    DebugLogEnabled := True;
  if GetEnvironmentVariable('FPIDE_GDBPROG') <> '' then
    GdbProgramName := GetEnvironmentVariable('FPIDE_GDBPROG');
end.