diff --git a/lazproj/coedit.lpi b/lazproj/coedit.lpi
index 72f2b1bb..239527df 100644
--- a/lazproj/coedit.lpi
+++ b/lazproj/coedit.lpi
@@ -522,7 +522,7 @@
-
+
@@ -836,6 +836,10 @@
+
+
+
+
diff --git a/lazproj/coedit.lpr b/lazproj/coedit.lpr
index 2e37598e..4d815f36 100644
--- a/lazproj/coedit.lpr
+++ b/lazproj/coedit.lpr
@@ -13,7 +13,7 @@ uses
ce_processes, ce_dialogs, ce_dubprojeditor, ce_controls, ce_dfmt,
ce_lcldragdrop, ce_stringrange, ce_dlangmaps, ce_projgroup, ce_projutils,
ce_d2synpresets, ce_dastworx, ce_dbgitf, ce_ddemangle, ce_dubproject,
- ce_halstead, ce_diff, ce_profileviewer, ce_semver, ce_term;
+ ce_halstead, ce_diff, ce_profileviewer, ce_semver, ce_term, ce_gdbmi2json;
{$R *.res}
diff --git a/src/ce_gdbmi2json.pas b/src/ce_gdbmi2json.pas
new file mode 100644
index 00000000..ab658bb0
--- /dev/null
+++ b/src/ce_gdbmi2json.pas
@@ -0,0 +1,705 @@
+unit ce_gdbmi2json;
+{$I ce_defines.inc}
+
+interface
+
+uses
+ Classes, SysUtils, fpjson, fgl;
+
+type
+
+ TTokenKind = (
+ tkInvalid, // on error, easier to leave parsing
+ tkNl, // #13#10 or #10, i.e te "nl" rule
+ tkToken, // unquoted text
+ tkAnd, // &
+ tkAt, // @
+ tkTiddle, // ~
+ tkComma, // ,
+ tkHat, // ^
+ tkStar, // *
+ tkLeftSquare, // [
+ tkRightSquare,// ]
+ tkLeftCurly, // {
+ tkRightCurly, // }
+ tkString, // quoted string (also used for "c-string" but no checked for now)
+ tkAss, // =
+ tkPlus, // +
+ tkGdb, // (gdb)
+ tkEOF // additional guard for safe lookups
+ );
+
+ PToken = ^TToken;
+ TToken = record
+ private
+ data: pchar;
+ length: longInt;
+ kind: TTokenKind;
+ public
+ function text: string;
+ end;
+
+ TTokenList = class(specialize TFPGList)
+ procedure popFront();
+ procedure clearAndDispose;
+ destructor destroy; override;
+ end;
+
+ TGdbMiNodeKind = (
+ gnkLogStreamOutput,
+ gnkTargetStreamOutput,
+ gnkConsoleStreamOutput
+ );
+
+(**
+ * Tokenizes and parses some GDB-MI output, according to the BNF grammar
+ * (https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Output-Syntax.html)
+ * and finally returns a JSON structure that follows grammar rules and names.
+ *)
+function gdbmi2json(const str: string): TJSONObject;
+
+implementation
+
+{$IFDEF DEBUG}
+var
+ r: TJSONObject;
+ t: TTokenList;
+{$ENDIF}
+
+procedure TTokenList.popFront();
+begin
+ dispose(Items[0]);
+ Delete(0);
+end;
+
+procedure TTokenList.clearAndDispose;
+begin
+ while count <> 0 do
+ popFront();
+end;
+
+destructor TTokenList.destroy;
+begin
+ inherited;
+ clearAndDispose;
+end;
+
+//TODO-cGDB: handle escapes in TToken.text()
+function TToken.text: string;
+begin
+ result := data[0 .. length];
+end;
+
+procedure lex(const str: string; list: TTokenList);
+var
+ i: integer;
+ s: integer;
+
+ procedure addToken(k: TTokenKind);
+ var
+ t: PToken;
+ begin
+ t := new(PToken);
+ t^.length:= i-s-1;
+ if (k <> tkEOF) and (k <> tkInvalid) then
+ t^.data:= @str[s]
+ else
+ t^.data:= nil;
+ if k = tkString then
+ begin
+ t^.data += 1;
+ t^.length -= 2;
+ end;
+ t^.kind:= k;
+ list.Add(t);
+ s := i;
+ end;
+
+begin
+ s := 1;
+ i := 1;
+ while i <= str.length do
+ begin
+ case str[i] of
+
+ '&':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkAnd);
+ end;
+
+ '@':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkAt);
+ end;
+
+ '+':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkPlus);
+ end;
+
+ '~':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkTiddle);
+ end;
+
+ '^':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkHat);
+ end;
+
+ '=':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkAss);
+ end;
+
+ ',':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkComma);
+ end;
+
+ '*':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkStar);
+ end;
+
+ '{':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkLeftCurly);
+ end;
+
+ '}':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkRightCurly);
+ end;
+
+ '[':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkLeftSquare);
+ end;
+
+ ']':
+ begin
+ i += 1;
+ addToken(TTokenKind.tkRightSquare);
+ end;
+
+ #10:
+ begin
+ i += 1;
+ addToken(TTokenKind.tkNl);
+ end;
+
+ #13:
+ begin
+ i += 1;
+ if str[i] = #10 then
+ begin
+ i += 1;
+ addToken(TTokenKind.tkNl);
+ end
+ else
+ begin
+ addToken(TTokenKind.tkInvalid);
+ end;
+ end;
+
+ '(':
+ begin
+ if (i + 4 <= str.length) and (str[i .. i + 4] = '(gdb)') then
+ begin
+ i += 5;
+ addToken(TTokenKind.tkGdb);
+ end
+ else
+ begin
+ addToken(TTokenKind.tkInvalid);
+ end;
+ end;
+
+ '"':
+ begin
+ while true do
+ begin
+ i += 1;
+ if i > str.length then
+ begin
+ addToken(TTokenKind.tkInvalid);
+ break;
+ end;
+ if str[i] = '\' then
+ begin
+ i += 1;
+ if str[i] = '"' then
+ i+= 1;
+ end;
+ if str[i] = '"' then
+ begin
+ i += 1;
+ addToken(TTokenKind.tkString);
+ break;
+ end;
+ end;
+ end
+
+ else
+ begin
+ while true do
+ begin
+ i += 1;
+ if i > str.length then
+ begin
+ addToken(TTokenKind.tkInvalid);
+ break;
+ end;
+ if str[i] in ['&', '~', '@', '=', '{', '}','[', ']', '*', '"', ',' ,
+ #10, #13] then
+ begin
+ addToken(TTokenKind.tkToken);
+ break;
+ end;
+ end;
+ end;
+
+ end;
+ end;
+
+ addToken(TTokenKind.tkEOF);
+
+end;
+
+(**
+ * BNF: log-stream-output → "&" c-string nl
+ *)
+function parseLogStreamOutput(tokens: TTokenList): TJSONObject;
+var
+ s: string;
+begin
+ if tokens[0]^.kind <> TTokenKind.tkAnd then
+ exit(nil);
+ tokens.popFront();
+ if tokens[0]^.kind <> TTokenKind.tkToken then
+ exit(nil);
+ s := tokens[0]^.text();
+ tokens.popFront();
+ if tokens[0]^.kind <> TTokenKind.tkNl then
+ exit(nil);
+ tokens.popFront();
+ result := TJSONObject.Create;
+ result['kind'] := TJSONIntegerNumber.Create(integer(gnkLogStreamOutput));
+ result['output'] := TJSONString.Create(s);
+end;
+
+(**
+ * BNF: target-stream-output → "@" c-string nl
+ *)
+function parseTargetStreamOutput(tokens: TTokenList): TJSONObject;
+var
+ s: string;
+begin
+ if tokens[0]^.kind <> TTokenKind.tkAt then
+ exit(nil);
+ tokens.popFront();
+ if tokens[0]^.kind <> TTokenKind.tkToken then
+ exit(nil);
+ s := tokens[0]^.text();
+ tokens.popFront();
+ if tokens[0]^.kind <> TTokenKind.tkNl then
+ exit(nil);
+ tokens.popFront();
+ result := TJSONObject.Create;
+ result['kind'] := TJSONIntegerNumber.Create(integer(gnkTargetStreamOutput));
+ result['output'] := TJSONString.Create(s);
+end;
+
+(**
+ * BNF: console-stream-output → "~" c-string nl
+ *)
+function parseConsoleStreamOutput(tokens: TTokenList): TJSONObject;
+var
+ s: string;
+begin
+ if tokens[0]^.kind <> TTokenKind.tkTiddle then
+ exit(nil);
+ tokens.popFront();
+ if tokens[0]^.kind <> TTokenKind.tkToken then
+ exit(nil);
+ s := tokens[0]^.text();
+ tokens.popFront();
+ if tokens[0]^.kind <> TTokenKind.tkNl then
+ exit(nil);
+ tokens.popFront();
+ result := TJSONObject.Create;
+ result['kind'] := TJSONIntegerNumber.Create(integer(gnkConsoleStreamOutput));
+ result['output'] := TJSONString.Create(s);
+end;
+
+function parseListValue(tokens: TTokenList): TJSONArray; forward;
+function parseTupleValue(tokens: TTokenList): TJSONObject; forward;
+function parseResult(tokens: TTokenList; obj: TJSONObject): boolean; forward;
+
+(**
+ * BNF: value → const | tuple | list
+ *)
+function parseValue(tokens: TTokenList): TJSONData;
+begin
+ result := nil;
+ case tokens[0]^.kind of
+ tkString:
+ begin
+ result := TJSONString.Create(tokens[0]^.text());
+ tokens.popFront();
+ end;
+ tkLeftCurly:
+ begin
+ result := parseTupleValue(tokens);
+ end;
+ tkLeftSquare:
+ begin
+ result := parseListValue(tokens);
+ end;
+ end;
+end;
+
+(**
+ * BNF: list → "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]"
+ *)
+function parseListValue(tokens: TTokenList): TJsonArray;
+var
+ r: TJSONData;
+begin
+ result := nil;
+ if tokens[0]^.kind <> TTokenKind.tkLeftSquare then
+ exit(nil);
+ tokens.popFront();
+ result := TJsonArray.Create();
+ if tokens[0]^.kind = TTokenKind.tkRightSquare then
+ begin
+ tokens.popFront();
+ exit;
+ end;
+ r := parseValue(tokens);
+ if r = nil then
+ begin
+ result.Free;
+ result := nil;
+ exit;
+ end;
+ result.Items[0] := r;
+ while tokens[0]^.kind = tkComma do
+ begin
+ tokens.popFront();
+ r := parseValue(tokens);
+ if r = nil then
+ begin
+ result.Free;
+ result := nil;
+ exit;
+ end;
+ result.Items[result.Count] := r;
+ end;
+ if tokens[0]^.kind <> tkRightSquare then
+ begin
+ result.Free;
+ result := nil;
+ end;
+ tokens.popFront();
+end;
+
+(**
+ * BNF: tuple → "{}" | "{" result ( "," result )* "}"
+ *)
+function parseTupleValue(tokens: TTokenList): TJSONObject;
+begin
+ result := nil;
+ if tokens[0]^.kind <> TTokenKind.tkLeftCurly then
+ exit(nil);
+ tokens.popFront();
+ result := TJSONObject.Create();
+ if tokens[0]^.kind = TTokenKind.tkRightCurly then
+ begin
+ tokens.popFront();
+ exit;
+ end;
+ if not parseResult(tokens, result) then
+ begin
+ result.Free;
+ result := nil;
+ exit;
+ end;
+ while tokens[0]^.kind = tkComma do
+ begin
+ tokens.popFront();
+ if not parseResult(tokens, result) then
+ begin
+ result.Free;
+ result := nil;
+ exit;
+ end;
+ end;
+ if tokens[0]^.kind <> tkRightCurly then
+ begin
+ result.Free;
+ result := nil;
+ end;
+ tokens.popFront();
+end;
+
+(**
+ * BNF: result → variable "=" value
+ *)
+function parseResult(tokens: TTokenList; obj: TJSONObject): boolean;
+var
+ v: TJSONData;
+ s: string;
+begin
+ result := false;
+ if tokens[0]^.kind <> TTokenKind.tkToken then
+ exit;
+
+ s := tokens[0]^.text();
+ tokens.popFront();
+ if tokens[0]^.kind <> TTokenKind.tkAss then
+ exit;
+
+ tokens.popFront();
+ v := parseValue(tokens);
+ if v = nil then
+ exit;
+
+ obj[s] := v;
+ result := true;
+end;
+
+(**
+ * BNF: result-record → [ token ] "^" result-class ( "," result )* nl
+ *)
+function parseResultRecord(tokens: TTokenList): TJSONObject;
+var
+ r: TJSONObject;
+begin
+ result := TJSONObject.Create;
+ if tokens[0]^.kind = TTokenKind.tkToken then
+ begin
+ result['token'] := TJSONString.Create(tokens[0]^.text());
+ tokens.popFront();
+ end;
+ if tokens[0]^.kind <> TTokenKind.tkHat then
+ begin
+ result.free;
+ exit(nil);
+ end;
+ tokens.popFront();
+ if tokens[0]^.kind <> TTokenKind.tkToken then
+ begin
+ result.free;
+ exit(nil);
+ end;
+ result['result-class'] := TJSONString.Create(tokens[0]^.text());
+ tokens.popFront();
+ r := TJSONObject.Create();
+ result['results'] := r;
+
+ while tokens[0]^.kind = TTokenKind.tkComma do
+ begin
+ tokens.popFront();
+ if not parseResult(tokens, r) then
+ begin
+ result.free;
+ result := nil;
+ exit;
+ end;
+ end;
+end;
+
+(**
+ * BNF: async-record → exec-async-output | status-async-output | notify-async-output
+ *)
+function parseAsyncRecord(tokens: TTokenList): TJSonObject;
+begin
+ //TODO-cGDB: parse async records
+ while tokens[0]^.kind <> tkNl do
+ tokens.popFront();
+ result := nil;
+end;
+
+(**
+ * BNF: stream-record → console-stream-output | target-stream-output | log-stream-output
+ *)
+function parseStreamRecord(tokens: TTokenList): TJSonObject;
+begin
+ result := nil;
+ if not assigned (result) then
+ result := parseConsoleStreamOutput(tokens);
+ if not assigned (result) then
+ result := parseTargetStreamOutput(tokens);
+ if not assigned (result) then
+ result := parseLogStreamOutput(tokens);
+end;
+
+function asyncRecordBegins(tokens: TTokenList): boolean;
+begin
+ result := ((tokens.Count > 0) and (tokens[0]^.kind in [tkStar, tkAss, tkPlus])) or
+ ((tokens.Count > 1) and (tokens[0]^.kind = tkToken)
+ and (tokens[1]^.kind in [tkStar, tkAss, tkPlus]));
+end;
+
+function streamRecordBegins(tokens: TTokenList): boolean;
+begin
+ result := (tokens.Count > 0) and (tokens[0]^.kind in [tkTiddle, tkAt, tkAnd]);
+end;
+
+(**
+ * BNF: out-of-band-record → async-record | stream-record
+ *)
+function parseOutOfBandRecord(tokens: TTokenList): TJSonObject;
+begin
+ if streamRecordBegins(tokens) then
+ result := parseStreamRecord(tokens)
+ else
+ result := parseAsyncRecord(tokens);
+end;
+
+function outOfBandRecordBegins(tokens: TTokenList): boolean;
+begin
+ result := asyncRecordBegins(tokens) or streamRecordBegins(tokens);
+end;
+
+(**
+ * BNF: output → ( out-of-band-record )* [ result-record ] "(gdb)" nl
+ *)
+function parseOutput(tokens: TTokenList): TJSonObject;
+var
+ a: TJSonArray;
+begin
+ result := TJSonObject.Create;
+ if outOfBandRecordBegins(tokens) then
+ begin
+ a := TJSONArray.Create;
+ result['out-of-band-records'] := a;
+ while outOfBandRecordBegins(tokens) do
+ a.Items[a.Count] := parseOutOfBandRecord(tokens);
+ end;
+ if tokens[0]^.kind <> tkGdb then
+ begin
+ result['result-record'] := parseResultRecord(tokens);
+ end;
+ if tokens[0]^.kind <> tkGdb then
+ begin
+ result.Free;
+ result := nil;
+ end;
+ tokens.popFront();
+ if tokens[0]^.kind <> tkNl then
+ begin
+ result.Free;
+ result := nil;
+ end;
+ tokens.popFront();
+ if tokens[0]^.kind <> tkEOF then
+ begin
+ result.Free;
+ result := nil;
+ end;
+end;
+
+function gdbmi2json(const str: string): TJSONObject;
+var
+ tokens: TTokenList;
+begin
+ result := nil;
+ tokens := TTokenList.Create;
+ try
+ lex(str, tokens);
+ if tokens.Count < 2 then
+ exit;
+ result := parseOutput(tokens);
+ finally
+ tokens.Free;
+ end;
+end;
+
+
+{$IFDEF DEBUG}
+begin
+
+ t := TTokenList.Create;
+ lex('^done,asm_insns=[{address="0x000107c0",func-name="main"}](gdb)'#10,t);
+
+ assert(t[0]^.text = '^');
+ assert(t[1]^.text = 'done');
+ assert(t[2]^.text = ',');
+ assert(t[3]^.text = 'asm_insns');
+ assert(t[4]^.text = '=');
+ assert(t[5]^.text = '[');
+ assert(t[6]^.text = '{');
+ assert(t[7]^.text = 'address');
+ assert(t[8]^.text = '=');
+ assert(t[9]^.text = '0x000107c0');
+ assert(t[10]^.text = ',');
+ assert(t[11]^.text = 'func-name');
+ assert(t[12]^.text = '=');
+ assert(t[13]^.text = 'main');
+ assert(t[14]^.text = '}');
+ assert(t[15]^.text = ']');
+ assert(t[16]^.text = '(gdb)');
+ assert(t[17]^.text = #10);
+ assert(t[18]^.kind = tkEOF);
+
+ r := parseOutput(t);
+
+ with TMemoryStream.Create do
+ try
+ WriteAnsiString(r.AsJSON);
+ SaveToFile('result-record-array-of-objects.json');
+ finally
+ free;
+ end;
+
+ t.Free;
+ r.Free;
+
+ ///////////////////////////////////////////////
+
+ t := TTokenList.Create;
+ lex('~abc'#10'@efg'#10'&hij'#10'(gdb)'#10,t);
+
+ assert(t[0]^.kind = tkTiddle);
+ assert(t[1]^.text = 'abc');
+ assert(t[2]^.kind = tkNl);
+ assert(t[3]^.kind = tkAt);
+ assert(t[4]^.text = 'efg');
+ assert(t[5]^.kind = tkNl);
+ assert(t[6]^.kind = tkAnd);
+ assert(t[7]^.text = 'hij');
+ assert(t[8]^.kind = tkNl);
+ assert(t[9]^.kind = tkGdb);
+ assert(t[10]^.kind = tkNl);
+ assert(t[11]^.kind = tkEOF);
+
+ r := parseOutput(t);
+
+ with TMemoryStream.Create do
+ try
+ WriteAnsiString(r.AsJSON);
+ SaveToFile('out-of-band-records.json');
+ finally
+ free;
+ end;
+
+ t.Free;
+ r.Free;
+
+{$ENDIF}
+end.
+