gdbmiwrap.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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. private
  54. function GetCount: LongInt;
  55. function GetValueAt(AIndex: LongInt): TGDBMI_Value;
  56. public
  57. property Count: LongInt read GetCount;
  58. property ValueAt [AIndex: LongInt]: TGDBMI_Value read GetValueAt;
  59. end;
  60. TGDBMI_AsyncOutput = class
  61. FAsyncClass: string;
  62. FParameters: TGDBMI_TupleValue;
  63. public
  64. constructor Create;
  65. destructor Destroy; override;
  66. procedure Clear;
  67. property AsyncClass: string read FAsyncClass write FAsyncClass;
  68. property Parameters: TGDBMI_TupleValue read FParameters;
  69. end;
  70. TGDBMI_ResultRecord = class(TGDBMI_AsyncOutput)
  71. public
  72. function Success: Boolean;
  73. end;
  74. TGDBMI_AsyncOutput_List = array of TGDBMI_AsyncOutput;
  75. TGDBWrapper = class
  76. private
  77. FProcess: TGDBProcess;
  78. FRawResponse: TStringList;
  79. FConsoleStream: TStringList;
  80. FExecAsyncOutput: TGDBMI_AsyncOutput;
  81. FResultRecord: TGDBMI_ResultRecord;
  82. function IsAlive: Boolean;
  83. procedure ReadResponse;
  84. public
  85. NotifyAsyncOutput: TGDBMI_AsyncOutput_List;
  86. constructor Create;
  87. destructor Destroy; override;
  88. procedure Command(S: string);
  89. procedure WaitForProgramStop;
  90. property RawResponse: TStringList read FRawResponse;
  91. property ConsoleStream: TStringList read FConsoleStream;
  92. property ExecAsyncOutput: TGDBMI_AsyncOutput read FExecAsyncOutput;
  93. property ResultRecord: TGDBMI_ResultRecord read FResultRecord write FResultRecord;
  94. property Alive: Boolean read IsAlive;
  95. end;
  96. implementation
  97. function TGDBMI_Value.AsString: string;
  98. begin
  99. Result := (self as TGDBMI_StringValue).StringValue;
  100. end;
  101. function TGDBMI_Value.AsLongInt: LongInt;
  102. begin
  103. Result := StrToInt(AsString);
  104. end;
  105. function TGDBMI_Value.AsPtrInt: PtrInt;
  106. begin
  107. {$ifdef CPU64}
  108. Result := StrToInt64(AsString);
  109. {$else}
  110. Result := StrToInt(AsString);
  111. {$endif}
  112. end;
  113. function TGDBMI_Value.AsTuple: TGDBMI_TupleValue;
  114. begin
  115. Result := self as TGDBMI_TupleValue;
  116. end;
  117. function TGDBMI_Value.AsList: TGDBMI_ListValue;
  118. begin
  119. Result := self as TGDBMI_ListValue;
  120. end;
  121. constructor TGDBMI_StringValue.Create(const S: string);
  122. begin
  123. FStringValue := S;
  124. end;
  125. destructor TGDBMI_TupleOrListValue.Destroy;
  126. begin
  127. Clear;
  128. inherited Destroy;
  129. end;
  130. procedure TGDBMI_TupleOrListValue.Clear;
  131. var
  132. I: LongInt;
  133. begin
  134. SetLength(FNames, 0);
  135. for I := Low(FValues) to High(FValues) do
  136. FreeAndNil(FValues[I]);
  137. SetLength(FValues, 0);
  138. end;
  139. procedure TGDBMI_TupleOrListValue.Add(AName: string; AValue: TGDBMI_Value);
  140. begin
  141. Assert(AValue <> nil);
  142. Assert(IsEmpty or (HasNames = (AName <> '')));
  143. if AName <> '' then
  144. begin
  145. SetLength(FNames, Length(FNames) + 1);
  146. FNames[Length(FNames) - 1] := AName;
  147. end;
  148. SetLength(FValues, Length(FValues) + 1);
  149. FValues[Length(FValues) - 1] := AValue;
  150. end;
  151. function TGDBMI_TupleOrListValue.HasNames: Boolean;
  152. begin
  153. Result := Length(FNames) > 0;
  154. end;
  155. function TGDBMI_TupleOrListValue.IsEmpty: Boolean;
  156. begin
  157. Result := Length(FValues) = 0;
  158. end;
  159. function TGDBMI_TupleOrListValue.GetValue(const AName: string): TGDBMI_Value;
  160. var
  161. I: LongInt;
  162. begin
  163. for I := Low(FNames) to High(FNames) do
  164. if FNames[I] = AName then
  165. begin
  166. Result := FValues[I];
  167. exit;
  168. end;
  169. Result := nil;
  170. end;
  171. function TGDBMI_ListValue.GetCount: LongInt;
  172. begin
  173. Result := Length(FValues);
  174. end;
  175. function TGDBMI_ListValue.GetValueAt(AIndex: LongInt): TGDBMI_Value;
  176. begin
  177. Assert((AIndex >= Low(FValues)) and (AIndex <= High(FValues)));
  178. Result := FValues[AIndex];
  179. end;
  180. constructor TGDBMI_AsyncOutput.Create;
  181. begin
  182. FParameters := TGDBMI_TupleValue.Create;
  183. end;
  184. destructor TGDBMI_AsyncOutput.Destroy;
  185. begin
  186. FParameters.Free;
  187. inherited Destroy;
  188. end;
  189. procedure TGDBMI_AsyncOutput.Clear;
  190. begin
  191. AsyncClass := '';
  192. Parameters.Clear;
  193. end;
  194. function TGDBMI_ResultRecord.Success: Boolean;
  195. begin
  196. { according to the GDB docs, 'done' and 'running' should be treated identically by clients }
  197. Result := (AsyncClass='done') or (AsyncClass='running');
  198. end;
  199. function ParseCString(const CStr: string; var NextCharPos: LongInt): string;
  200. begin
  201. if (NextCharPos <= Length(CStr)) and (CStr[NextCharPos] = '"') then
  202. Inc(NextCharPos);
  203. Result := '';
  204. while NextCharPos <= Length(CStr) do
  205. begin
  206. if CStr[NextCharPos] = '"' then
  207. begin
  208. Inc(NextCharPos);
  209. exit;
  210. end
  211. else if CStr[NextCharPos] = '\' then
  212. begin
  213. Inc(NextCharPos);
  214. if NextCharPos <= Length(CStr) then
  215. case CStr[NextCharPos] of
  216. '''': Result := Result + '''';
  217. '"': Result := Result + '"';
  218. 'n': Result := Result + #10;
  219. 'r': Result := Result + #13;
  220. 't': Result := Result + #9;
  221. 'v': Result := Result + #11;
  222. 'b': Result := Result + #8;
  223. 'f': Result := Result + #12;
  224. 'a': Result := Result + #7;
  225. '\': Result := Result + '\';
  226. '?': Result := Result + '?';
  227. {\0, \000, \xhhh}
  228. end;
  229. end
  230. else
  231. Result := Result + CStr[NextCharPos];
  232. Inc(NextCharPos);
  233. end;
  234. end;
  235. function ParseIdentifier(const S: string; var NextCharPos: LongInt): string;
  236. begin
  237. Result := '';
  238. while (NextCharPos <= Length(S)) and (S[NextCharPos] in ['A'..'Z', 'a'..'z', '0'..'9', '-']) do
  239. begin
  240. Result := Result + S[NextCharPos];
  241. Inc(NextCharPos);
  242. end;
  243. end;
  244. function ParseValue(const S: string; var NextCharPos: LongInt): TGDBMI_Value;
  245. var
  246. CStr: string;
  247. Tuple: TGDBMI_TupleValue;
  248. List: TGDBMI_ListValue;
  249. Name: string;
  250. Value: TGDBMI_Value;
  251. begin
  252. Assert(NextCharPos <= Length(S));
  253. case S[NextCharPos] of
  254. '"':
  255. begin
  256. CStr := ParseCString(S, NextCharPos);
  257. Result := TGDBMI_StringValue.Create(CStr);
  258. end;
  259. '{':
  260. begin
  261. Inc(NextCharPos);
  262. Assert(NextCharPos <= Length(S));
  263. Tuple := TGDBMI_TupleValue.Create;
  264. Result := Tuple;
  265. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> '}') do
  266. begin
  267. Name := ParseIdentifier(S, NextCharPos);
  268. Assert(NextCharPos <= Length(S));
  269. Assert(S[NextCharPos] = '=');
  270. Inc(NextCharPos);
  271. Value := ParseValue(S, NextCharPos);
  272. Tuple.Add(Name, Value);
  273. Assert(NextCharPos <= Length(S));
  274. Assert(S[NextCharPos] in [',', '}']);
  275. if S[NextCharPos] = ',' then
  276. Inc(NextCharPos);
  277. end;
  278. if (NextCharPos <= Length(S)) and (S[NextCharPos] = '}') then
  279. Inc(NextCharPos);
  280. end;
  281. '[':
  282. begin
  283. Inc(NextCharPos);
  284. Assert(NextCharPos <= Length(S));
  285. List := TGDBMI_ListValue.Create;
  286. Result := List;
  287. if S[NextCharPos] in ['"', '{', '['] then
  288. begin
  289. { list of values, no names }
  290. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> ']') do
  291. begin
  292. Value := ParseValue(S, NextCharPos);
  293. List.Add('', Value);
  294. Assert(NextCharPos <= Length(S));
  295. Assert(S[NextCharPos] in [',', ']']);
  296. if S[NextCharPos] = ',' then
  297. Inc(NextCharPos);
  298. end;
  299. end
  300. else
  301. begin
  302. { list of name=value pairs (like a tuple) }
  303. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> ']') do
  304. begin
  305. Name := ParseIdentifier(S, NextCharPos);
  306. Assert(NextCharPos <= Length(S));
  307. Assert(S[NextCharPos] = '=');
  308. Inc(NextCharPos);
  309. Value := ParseValue(S, NextCharPos);
  310. List.Add(Name, Value);
  311. Assert(NextCharPos <= Length(S));
  312. Assert(S[NextCharPos] in [',', ']']);
  313. if S[NextCharPos] = ',' then
  314. Inc(NextCharPos);
  315. end;
  316. end;
  317. if (NextCharPos <= Length(S)) and (S[NextCharPos] = ']') then
  318. Inc(NextCharPos);
  319. end;
  320. else
  321. Assert(False);
  322. end;
  323. end;
  324. procedure ParseAsyncOutput(const S: string; AsyncOutput: TGDBMI_AsyncOutput; var NextCharPos: LongInt);
  325. var
  326. Name: string;
  327. Value: TGDBMI_Value;
  328. begin
  329. AsyncOutput.Clear;
  330. AsyncOutput.AsyncClass := ParseIdentifier(S, NextCharPos);
  331. while NextCharPos <= Length(S) do
  332. begin
  333. Assert(S[NextCharPos] = ',');
  334. Inc(NextCharPos);
  335. Name := ParseIdentifier(S, NextCharPos);
  336. Assert(NextCharPos <= Length(S));
  337. Assert(S[NextCharPos] = '=');
  338. Inc(NextCharPos);
  339. Value := ParseValue(S, NextCharPos);
  340. AsyncOutput.Parameters.Add(Name, Value);
  341. end;
  342. end;
  343. function TGDBWrapper.IsAlive: Boolean;
  344. begin
  345. Result := Assigned(FProcess) and FProcess.Alive;
  346. end;
  347. procedure TGDBWrapper.ReadResponse;
  348. var
  349. S: string;
  350. I: LongInt;
  351. NextCharPos: LongInt;
  352. NAO: TGDBMI_AsyncOutput;
  353. begin
  354. FRawResponse.Clear;
  355. FConsoleStream.Clear;
  356. ExecAsyncOutput.Clear;
  357. for I := Low(NotifyAsyncOutput) to High(NotifyAsyncOutput) do
  358. FreeAndNil(NotifyAsyncOutput[I]);
  359. SetLength(NotifyAsyncOutput, 0);
  360. if not FProcess.Alive then
  361. exit;
  362. repeat
  363. S := FProcess.GDBReadLn;
  364. FRawResponse.Add(S);
  365. if Length(S) >= 1 then
  366. case S[1] of
  367. '~':
  368. begin
  369. NextCharPos := 2;
  370. FConsoleStream.Add(ParseCString(S, NextCharPos));
  371. end;
  372. '*':
  373. begin
  374. NextCharPos := 2;
  375. ParseAsyncOutput(S, ExecAsyncOutput, NextCharPos);
  376. end;
  377. '^':
  378. begin
  379. NextCharPos := 2;
  380. ParseAsyncOutput(S, ResultRecord, NextCharPos);
  381. end;
  382. '=':
  383. begin
  384. NextCharPos := 2;
  385. NAO := TGDBMI_AsyncOutput.Create;
  386. try
  387. ParseAsyncOutput(S, NAO, NextCharPos);
  388. SetLength(NotifyAsyncOutput, Length(NotifyAsyncOutput) + 1);
  389. NotifyAsyncOutput[Length(NotifyAsyncOutput) - 1] := NAO;
  390. NAO := nil;
  391. finally
  392. NAO.Free;
  393. end;
  394. end;
  395. end;
  396. until (S = '(gdb) ') or (S = '(gdb)') or not FProcess.Alive;
  397. end;
  398. constructor TGDBWrapper.Create;
  399. begin
  400. FRawResponse := TStringList.Create;
  401. FConsoleStream := TStringList.Create;
  402. FProcess := TGDBProcess.Create;
  403. FExecAsyncOutput := TGDBMI_AsyncOutput.Create;
  404. FResultRecord := TGDBMI_ResultRecord.Create;
  405. ReadResponse;
  406. end;
  407. destructor TGDBWrapper.Destroy;
  408. begin
  409. if Alive then
  410. Command('-gdb-exit');
  411. FProcess.Free;
  412. FResultRecord.Free;
  413. FExecAsyncOutput.Free;
  414. FConsoleStream.Free;
  415. FRawResponse.Free;
  416. end;
  417. procedure TGDBWrapper.Command(S: string);
  418. begin
  419. FProcess.GDBWriteLn(S);
  420. ReadResponse;
  421. end;
  422. procedure TGDBWrapper.WaitForProgramStop;
  423. begin
  424. repeat
  425. ReadResponse;
  426. until (ExecAsyncOutput.AsyncClass = 'stopped') or not FProcess.Alive;
  427. end;
  428. end.