gdbmiwrap.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. {
  2. Copyright (c) 2015 by Nikolay Nikolov
  3. This unit provides a wrapper around GDB and implements parsing of
  4. the GDB/MI command result records.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit gdbmiwrap;
  12. {$MODE objfpc}{$H+}
  13. {$ASSERTIONS on}
  14. interface
  15. uses
  16. SysUtils, Classes, GDBMIProc;
  17. type
  18. TGDBMI_TupleValue = class;
  19. TGDBMI_ListValue = class;
  20. TGDBMI_Value = class
  21. function AsString: string;
  22. function AsLongInt: LongInt;
  23. function AsPtrInt: PtrInt;
  24. function AsTuple: TGDBMI_TupleValue;
  25. function AsList: TGDBMI_ListValue;
  26. end;
  27. { "C string\n" }
  28. TGDBMI_StringValue = class(TGDBMI_Value)
  29. FStringValue: string;
  30. public
  31. constructor Create(const S: string);
  32. property StringValue: string read FStringValue;
  33. end;
  34. (* {...} or [...] *)
  35. TGDBMI_TupleOrListValue = class(TGDBMI_Value)
  36. private
  37. FNames: array of string;
  38. FValues: array of TGDBMI_Value;
  39. function GetValue(const AName: string): TGDBMI_Value;
  40. public
  41. destructor Destroy; override;
  42. procedure Clear;
  43. procedure Add(AName: string; AValue: TGDBMI_Value);
  44. function HasNames: Boolean;
  45. function IsEmpty: Boolean;
  46. property Values [const AName: string]: TGDBMI_Value read GetValue; default;
  47. end;
  48. (* {} or {variable=value,variable=value,variable=value} *)
  49. TGDBMI_TupleValue = class(TGDBMI_TupleOrListValue)
  50. end;
  51. { [] or [value,value,value] or [variable=value,variable=value,variable=value] }
  52. TGDBMI_ListValue = class(TGDBMI_TupleOrListValue)
  53. end;
  54. TGDBMI_AsyncOutput = class
  55. FAsyncClass: string;
  56. FParameters: TGDBMI_TupleValue;
  57. public
  58. constructor Create;
  59. destructor Destroy; override;
  60. procedure Clear;
  61. property AsyncClass: string read FAsyncClass write FAsyncClass;
  62. property Parameters: TGDBMI_TupleValue read FParameters;
  63. end;
  64. TGDBMI_AsyncOutput_List = array of TGDBMI_AsyncOutput;
  65. TGDBWrapper = class
  66. private
  67. FProcess: TGDBProcess;
  68. FRawResponse: TStringList;
  69. FConsoleStream: TStringList;
  70. FExecAsyncOutput: TGDBMI_AsyncOutput;
  71. FResultRecord: TGDBMI_AsyncOutput;
  72. function IsAlive: Boolean;
  73. procedure ReadResponse;
  74. public
  75. NotifyAsyncOutput: TGDBMI_AsyncOutput_List;
  76. constructor Create;
  77. destructor Destroy; override;
  78. procedure Command(S: string);
  79. procedure WaitForProgramStop;
  80. property RawResponse: TStringList read FRawResponse;
  81. property ConsoleStream: TStringList read FConsoleStream;
  82. property ExecAsyncOutput: TGDBMI_AsyncOutput read FExecAsyncOutput;
  83. property ResultRecord: TGDBMI_AsyncOutput read FResultRecord write FResultRecord;
  84. property Alive: Boolean read IsAlive;
  85. end;
  86. implementation
  87. function TGDBMI_Value.AsString: string;
  88. begin
  89. Result := (self as TGDBMI_StringValue).StringValue;
  90. end;
  91. function TGDBMI_Value.AsLongInt: LongInt;
  92. begin
  93. Result := StrToInt(AsString);
  94. end;
  95. function TGDBMI_Value.AsPtrInt: PtrInt;
  96. begin
  97. {$ifdef CPU64}
  98. Result := StrToInt64(AsString);
  99. {$else}
  100. Result := StrToInt(AsString);
  101. {$endif}
  102. end;
  103. function TGDBMI_Value.AsTuple: TGDBMI_TupleValue;
  104. begin
  105. Result := self as TGDBMI_TupleValue;
  106. end;
  107. function TGDBMI_Value.AsList: TGDBMI_ListValue;
  108. begin
  109. Result := self as TGDBMI_ListValue;
  110. end;
  111. constructor TGDBMI_StringValue.Create(const S: string);
  112. begin
  113. FStringValue := S;
  114. end;
  115. destructor TGDBMI_TupleOrListValue.Destroy;
  116. begin
  117. Clear;
  118. inherited Destroy;
  119. end;
  120. procedure TGDBMI_TupleOrListValue.Clear;
  121. var
  122. I: LongInt;
  123. begin
  124. SetLength(FNames, 0);
  125. for I := Low(FValues) to High(FValues) do
  126. FreeAndNil(FValues[I]);
  127. SetLength(FValues, 0);
  128. end;
  129. procedure TGDBMI_TupleOrListValue.Add(AName: string; AValue: TGDBMI_Value);
  130. begin
  131. Assert(AValue <> nil);
  132. Assert(IsEmpty or (HasNames = (AName <> '')));
  133. if AName <> '' then
  134. begin
  135. SetLength(FNames, Length(FNames) + 1);
  136. FNames[Length(FNames) - 1] := AName;
  137. end;
  138. SetLength(FValues, Length(FValues) + 1);
  139. FValues[Length(FValues) - 1] := AValue;
  140. end;
  141. function TGDBMI_TupleOrListValue.HasNames: Boolean;
  142. begin
  143. Result := Length(FNames) > 0;
  144. end;
  145. function TGDBMI_TupleOrListValue.IsEmpty: Boolean;
  146. begin
  147. Result := Length(FValues) = 0;
  148. end;
  149. function TGDBMI_TupleOrListValue.GetValue(const AName: string): TGDBMI_Value;
  150. var
  151. I: LongInt;
  152. begin
  153. for I := Low(FNames) to High(FNames) do
  154. if FNames[I] = AName then
  155. begin
  156. Result := FValues[I];
  157. exit;
  158. end;
  159. Result := nil;
  160. end;
  161. constructor TGDBMI_AsyncOutput.Create;
  162. begin
  163. FParameters := TGDBMI_TupleValue.Create;
  164. end;
  165. destructor TGDBMI_AsyncOutput.Destroy;
  166. begin
  167. FParameters.Free;
  168. inherited Destroy;
  169. end;
  170. procedure TGDBMI_AsyncOutput.Clear;
  171. begin
  172. AsyncClass := '';
  173. Parameters.Clear;
  174. end;
  175. function ParseCString(const CStr: string; var NextCharPos: LongInt): string;
  176. begin
  177. if (NextCharPos <= Length(CStr)) and (CStr[NextCharPos] = '"') then
  178. Inc(NextCharPos);
  179. Result := '';
  180. while NextCharPos <= Length(CStr) do
  181. begin
  182. if CStr[NextCharPos] = '"' then
  183. begin
  184. Inc(NextCharPos);
  185. exit;
  186. end
  187. else if CStr[NextCharPos] = '\' then
  188. begin
  189. Inc(NextCharPos);
  190. if NextCharPos <= Length(CStr) then
  191. case CStr[NextCharPos] of
  192. '''': Result := Result + '''';
  193. '"': Result := Result + '"';
  194. 'n': Result := Result + #10;
  195. 'r': Result := Result + #13;
  196. 't': Result := Result + #9;
  197. 'v': Result := Result + #11;
  198. 'b': Result := Result + #8;
  199. 'f': Result := Result + #12;
  200. 'a': Result := Result + #7;
  201. '\': Result := Result + '\';
  202. '?': Result := Result + '?';
  203. {\0, \000, \xhhh}
  204. end;
  205. end
  206. else
  207. Result := Result + CStr[NextCharPos];
  208. Inc(NextCharPos);
  209. end;
  210. end;
  211. function ParseIdentifier(const S: string; var NextCharPos: LongInt): string;
  212. begin
  213. Result := '';
  214. while (NextCharPos <= Length(S)) and (S[NextCharPos] in ['A'..'Z', 'a'..'z', '0'..'9', '-']) do
  215. begin
  216. Result := Result + S[NextCharPos];
  217. Inc(NextCharPos);
  218. end;
  219. end;
  220. function ParseValue(const S: string; var NextCharPos: LongInt): TGDBMI_Value;
  221. var
  222. CStr: string;
  223. Tuple: TGDBMI_TupleValue;
  224. List: TGDBMI_ListValue;
  225. Name: string;
  226. Value: TGDBMI_Value;
  227. begin
  228. Assert(NextCharPos <= Length(S));
  229. case S[NextCharPos] of
  230. '"':
  231. begin
  232. CStr := ParseCString(S, NextCharPos);
  233. Result := TGDBMI_StringValue.Create(CStr);
  234. end;
  235. '{':
  236. begin
  237. Inc(NextCharPos);
  238. Assert(NextCharPos <= Length(S));
  239. Tuple := TGDBMI_TupleValue.Create;
  240. Result := Tuple;
  241. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> '}') do
  242. begin
  243. Name := ParseIdentifier(S, NextCharPos);
  244. Assert(NextCharPos <= Length(S));
  245. Assert(S[NextCharPos] = '=');
  246. Inc(NextCharPos);
  247. Value := ParseValue(S, NextCharPos);
  248. Tuple.Add(Name, Value);
  249. Assert(NextCharPos <= Length(S));
  250. Assert(S[NextCharPos] in [',', '}']);
  251. if S[NextCharPos] = ',' then
  252. Inc(NextCharPos);
  253. end;
  254. if (NextCharPos <= Length(S)) and (S[NextCharPos] = '}') then
  255. Inc(NextCharPos);
  256. end;
  257. '[':
  258. begin
  259. Inc(NextCharPos);
  260. Assert(NextCharPos <= Length(S));
  261. List := TGDBMI_ListValue.Create;
  262. Result := List;
  263. if S[NextCharPos] in ['"', '{', '['] then
  264. begin
  265. { list of values, no names }
  266. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> ']') do
  267. begin
  268. Value := ParseValue(S, NextCharPos);
  269. List.Add('', Value);
  270. Assert(NextCharPos <= Length(S));
  271. Assert(S[NextCharPos] in [',', ']']);
  272. if S[NextCharPos] = ',' then
  273. Inc(NextCharPos);
  274. end;
  275. end
  276. else
  277. begin
  278. { list of name=value pairs (like a tuple) }
  279. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> ']') do
  280. begin
  281. Name := ParseIdentifier(S, NextCharPos);
  282. Assert(NextCharPos <= Length(S));
  283. Assert(S[NextCharPos] = '=');
  284. Inc(NextCharPos);
  285. Value := ParseValue(S, NextCharPos);
  286. List.Add(Name, Value);
  287. Assert(NextCharPos <= Length(S));
  288. Assert(S[NextCharPos] in [',', ']']);
  289. if S[NextCharPos] = ',' then
  290. Inc(NextCharPos);
  291. end;
  292. end;
  293. if (NextCharPos <= Length(S)) and (S[NextCharPos] = ']') then
  294. Inc(NextCharPos);
  295. end;
  296. else
  297. Assert(False);
  298. end;
  299. end;
  300. procedure ParseAsyncOutput(const S: string; AsyncOutput: TGDBMI_AsyncOutput; var NextCharPos: LongInt);
  301. var
  302. Name: string;
  303. Value: TGDBMI_Value;
  304. begin
  305. AsyncOutput.Clear;
  306. AsyncOutput.AsyncClass := ParseIdentifier(S, NextCharPos);
  307. while NextCharPos <= Length(S) do
  308. begin
  309. Assert(S[NextCharPos] = ',');
  310. Inc(NextCharPos);
  311. Name := ParseIdentifier(S, NextCharPos);
  312. Assert(NextCharPos <= Length(S));
  313. Assert(S[NextCharPos] = '=');
  314. Inc(NextCharPos);
  315. Value := ParseValue(S, NextCharPos);
  316. AsyncOutput.Parameters.Add(Name, Value);
  317. end;
  318. end;
  319. function TGDBWrapper.IsAlive: Boolean;
  320. begin
  321. Result := Assigned(FProcess) and FProcess.Alive;
  322. end;
  323. procedure TGDBWrapper.ReadResponse;
  324. var
  325. S: string;
  326. I: LongInt;
  327. NextCharPos: LongInt;
  328. NAO: TGDBMI_AsyncOutput;
  329. begin
  330. FRawResponse.Clear;
  331. FConsoleStream.Clear;
  332. for I := Low(NotifyAsyncOutput) to High(NotifyAsyncOutput) do
  333. FreeAndNil(NotifyAsyncOutput[I]);
  334. SetLength(NotifyAsyncOutput, 0);
  335. if not FProcess.Alive then
  336. exit;
  337. repeat
  338. S := FProcess.GDBReadLn;
  339. FRawResponse.Add(S);
  340. if Length(S) >= 1 then
  341. case S[1] of
  342. '~':
  343. begin
  344. NextCharPos := 2;
  345. FConsoleStream.Add(ParseCString(S, NextCharPos));
  346. end;
  347. '*':
  348. begin
  349. NextCharPos := 2;
  350. ParseAsyncOutput(S, ExecAsyncOutput, NextCharPos);
  351. end;
  352. '^':
  353. begin
  354. NextCharPos := 2;
  355. ParseAsyncOutput(S, ResultRecord, NextCharPos);
  356. end;
  357. '=':
  358. begin
  359. NextCharPos := 2;
  360. NAO := TGDBMI_AsyncOutput.Create;
  361. try
  362. ParseAsyncOutput(S, NAO, NextCharPos);
  363. SetLength(NotifyAsyncOutput, Length(NotifyAsyncOutput) + 1);
  364. NotifyAsyncOutput[Length(NotifyAsyncOutput) - 1] := NAO;
  365. NAO := nil;
  366. finally
  367. NAO.Free;
  368. end;
  369. end;
  370. end;
  371. until (S = '(gdb) ') or (S = '(gdb)') or not FProcess.Alive;
  372. end;
  373. constructor TGDBWrapper.Create;
  374. begin
  375. FRawResponse := TStringList.Create;
  376. FConsoleStream := TStringList.Create;
  377. FProcess := TGDBProcess.Create;
  378. FExecAsyncOutput := TGDBMI_AsyncOutput.Create;
  379. FResultRecord := TGDBMI_AsyncOutput.Create;
  380. ReadResponse;
  381. end;
  382. destructor TGDBWrapper.Destroy;
  383. begin
  384. if Alive then
  385. Command('-gdb-exit');
  386. FProcess.Free;
  387. FResultRecord.Free;
  388. FExecAsyncOutput.Free;
  389. FConsoleStream.Free;
  390. FRawResponse.Free;
  391. end;
  392. procedure TGDBWrapper.Command(S: string);
  393. begin
  394. FProcess.GDBWriteLn(S);
  395. ReadResponse;
  396. end;
  397. procedure TGDBWrapper.WaitForProgramStop;
  398. begin
  399. repeat
  400. ReadResponse;
  401. until (ExecAsyncOutput.AsyncClass = 'stopped') or not FProcess.Alive;
  402. end;
  403. end.