Initial commit
This commit is contained in:
commit
4440bfbe18
18 changed files with 1616 additions and 0 deletions
32
src/backend/backend.ts
Normal file
32
src/backend/backend.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
export interface Breakpoint {
|
||||
file: string;
|
||||
line: number;
|
||||
}
|
||||
|
||||
export interface Stack {
|
||||
level: number;
|
||||
address: string;
|
||||
function: string;
|
||||
fileName: string;
|
||||
file: string;
|
||||
line: number;
|
||||
}
|
||||
|
||||
export interface IBackend {
|
||||
load(cwd: string, target: string): Thenable<any>;
|
||||
start(): Thenable<boolean>;
|
||||
stop();
|
||||
interrupt(): Thenable<boolean>;
|
||||
continue(): Thenable<boolean>;
|
||||
next(): Thenable<boolean>;
|
||||
step(): Thenable<boolean>;
|
||||
stepOut(): Thenable<boolean>;
|
||||
loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]>;
|
||||
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>;
|
||||
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean>;
|
||||
clearBreakPoints(): Thenable<any>;
|
||||
getStack(maxLevels: number): Thenable<Stack[]>;
|
||||
getStackVariables(thread: number, frame: number): Thenable<[string, string][]>;
|
||||
evalExpression(name: string): Thenable<any>;
|
||||
isReady(): boolean;
|
||||
}
|
||||
201
src/backend/gdb_expansion.ts
Normal file
201
src/backend/gdb_expansion.ts
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
{
|
||||
quit = false,
|
||||
_views = {
|
||||
{
|
||||
view = 0x7ffff7ece1e8,
|
||||
renderer = 0x7ffff7eccc50,
|
||||
world = 0x7ffff7ece480
|
||||
}
|
||||
},
|
||||
deltaTimer = {
|
||||
_flagStarted = false,
|
||||
_timeStart = {length = 0},
|
||||
_timeMeasured = {length = 0}
|
||||
},
|
||||
_start = {callbacks = 0x0},
|
||||
_stop = {callbacks = 0x0}
|
||||
}
|
||||
*/
|
||||
|
||||
const resultRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*)\s*=\s*/;
|
||||
const variableRegex = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/;
|
||||
const errorRegex = /^\<.+?\>/;
|
||||
const referenceRegex = /^0x[0-9a-fA-F]+/;
|
||||
const numberRegex = /^[0-9]+/;
|
||||
|
||||
export function isExpandable(value: string): number {
|
||||
let primitive: any;
|
||||
let match;
|
||||
value = value.trim();
|
||||
if (value.length == 0) return 0;
|
||||
else if (value[0] == '{') return 1; // object
|
||||
else if (value.startsWith("true")) return 0;
|
||||
else if (value.startsWith("false")) return 0;
|
||||
else if (value.startsWith("0x0")) return 0;
|
||||
else if (match = referenceRegex.exec(value)) return 2; // reference
|
||||
else if (match = numberRegex.exec(value)) return 0;
|
||||
else if (match = variableRegex.exec(value)) return 0;
|
||||
else if (match = errorRegex.exec(value)) return 0;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
export function expandValue(variableCreate: Function, value: string): any {
|
||||
let parseCString = () => {
|
||||
value = value.trim();
|
||||
if (value[0] != '"')
|
||||
return "";
|
||||
let stringEnd = 1;
|
||||
let inString = true;
|
||||
let remaining = value.substr(1);
|
||||
let escaped = false;
|
||||
while (inString) {
|
||||
if (escaped)
|
||||
escaped = false;
|
||||
else if (remaining[0] == '\\')
|
||||
escaped = true;
|
||||
else if (remaining[0] == '"')
|
||||
inString = false;
|
||||
|
||||
remaining = remaining.substr(1);
|
||||
stringEnd++;
|
||||
}
|
||||
let str = value.substr(0, stringEnd).trim();
|
||||
value = value.substr(stringEnd).trim();
|
||||
return str;
|
||||
};
|
||||
|
||||
let parseValue, parseCommaResult, parseCommaValue, parseResult;
|
||||
|
||||
let parseTupleOrList = () => {
|
||||
value = value.trim();
|
||||
if (value[0] != '{')
|
||||
return undefined;
|
||||
let oldContent = value;
|
||||
value = value.substr(1).trim();
|
||||
if (value[0] == '}')
|
||||
return [];
|
||||
let eqPos = value.indexOf("=");
|
||||
let newValPos1 = value.indexOf("{");
|
||||
let newValPos2 = value.indexOf(",");
|
||||
let newValPos = newValPos1;
|
||||
if (newValPos2 != -1 && newValPos2 < newValPos1)
|
||||
newValPos = newValPos2;
|
||||
if (newValPos != -1 && eqPos > newValPos || eqPos == -1) { // is value list
|
||||
let values = [];
|
||||
let val = parseValue();
|
||||
values.push(val);
|
||||
let remaining = value;
|
||||
while (val = parseCommaValue())
|
||||
values.push(val);
|
||||
value = value.substr(1).trim(); // }
|
||||
return values;
|
||||
}
|
||||
|
||||
let result = parseResult();
|
||||
if (result) {
|
||||
let results = [];
|
||||
results.push(result);
|
||||
while (result = parseCommaResult())
|
||||
results.push(result);
|
||||
value = value.substr(1).trim(); // }
|
||||
return results;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
let parsePrimitive = () => {
|
||||
let primitive: any;
|
||||
let match;
|
||||
value = value.trim();
|
||||
if (value.length == 0)
|
||||
primitive = undefined;
|
||||
else if (value.startsWith("true")) {
|
||||
primitive = "true";
|
||||
value = value.substr(4).trim();
|
||||
}
|
||||
else if (value.startsWith("false")) {
|
||||
primitive = "false";
|
||||
value = value.substr(5).trim();
|
||||
}
|
||||
else if (value.startsWith("0x0")) {
|
||||
primitive = "<nullptr>";
|
||||
value = value.substr(3).trim();
|
||||
}
|
||||
else if (match = referenceRegex.exec(value)) {
|
||||
primitive = "*" + match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
}
|
||||
else if (match = numberRegex.exec(value)) {
|
||||
primitive = match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
}
|
||||
else if (match = variableRegex.exec(value)) {
|
||||
primitive = match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
}
|
||||
else if (match = errorRegex.exec(value)) {
|
||||
primitive = match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
}
|
||||
else {
|
||||
primitive = "<???>";
|
||||
}
|
||||
return primitive;
|
||||
};
|
||||
|
||||
parseValue = () => {
|
||||
value = value.trim();
|
||||
if (value[0] == '"')
|
||||
return parseCString();
|
||||
else if (value[0] == '{')
|
||||
return parseTupleOrList();
|
||||
else
|
||||
return parsePrimitive();
|
||||
};
|
||||
|
||||
parseResult = () => {
|
||||
value = value.trim();
|
||||
let variableMatch = resultRegex.exec(value);
|
||||
if (!variableMatch)
|
||||
return undefined;
|
||||
value = value.substr(variableMatch[0].length).trim();
|
||||
let variable = variableMatch[1];
|
||||
let val = parseValue();
|
||||
let ref = 0;
|
||||
if (typeof val == "object") {
|
||||
ref = variableCreate(val);
|
||||
val = "Object";
|
||||
}
|
||||
if (typeof val == "string" && val.startsWith("*0x")) {
|
||||
ref = variableCreate("*" + variable);
|
||||
val = "Object@" + val;
|
||||
}
|
||||
return {
|
||||
name: variable,
|
||||
value: val,
|
||||
variablesReference: ref
|
||||
};
|
||||
};
|
||||
|
||||
parseCommaValue = () => {
|
||||
value = value.trim();
|
||||
if (value[0] != ',')
|
||||
return undefined;
|
||||
value = value.substr(1).trim();
|
||||
return parseValue();
|
||||
};
|
||||
|
||||
parseCommaResult = () => {
|
||||
value = value.trim();
|
||||
if (value[0] != ',')
|
||||
return undefined;
|
||||
value = value.substr(1).trim();
|
||||
return parseResult();
|
||||
};
|
||||
|
||||
|
||||
value = value.trim();
|
||||
return parseValue();
|
||||
}
|
||||
278
src/backend/mi2/mi2.ts
Normal file
278
src/backend/mi2/mi2.ts
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
import { Breakpoint, IBackend, Stack } from "../backend.ts"
|
||||
import * as ChildProcess from "child_process"
|
||||
import { EventEmitter } from "events"
|
||||
import { parseMI, MINode } from '../mi_parse';
|
||||
|
||||
export class MI2 extends EventEmitter implements IBackend {
|
||||
constructor(public application: string, public preargs: string[]) {
|
||||
super();
|
||||
}
|
||||
|
||||
load(cwd: string, target: string): Thenable<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.process = ChildProcess.spawn(this.application, this.preargs.concat([target]), { cwd: cwd });
|
||||
this.process.stdout.on("data", this.stdout.bind(this));
|
||||
this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
|
||||
Promise.all([
|
||||
this.sendCommand("environment-directory \"" + cwd + "\"")
|
||||
]).then(resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
stdout(data) {
|
||||
this.buffer += data.toString("utf8");
|
||||
let end = this.buffer.lastIndexOf('\n');
|
||||
if (end != -1) {
|
||||
this.onOutput(this.buffer.substr(0, end));
|
||||
this.buffer = this.buffer.substr(end + 1);
|
||||
}
|
||||
}
|
||||
|
||||
onOutput(lines) {
|
||||
lines = <string[]>lines.split('\n');
|
||||
lines.forEach(line => {
|
||||
let parsed = parseMI(line);
|
||||
//this.log("log", JSON.stringify(parsed));
|
||||
let handled = false;
|
||||
if (parsed.token !== undefined) {
|
||||
if (this.handlers[parsed.token]) {
|
||||
this.handlers[parsed.token](parsed);
|
||||
delete this.handlers[parsed.token];
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
if (parsed.resultRecords && parsed.resultRecords.resultClass == "error") {
|
||||
this.log("log", "An error occured: " + parsed.result("msg"));
|
||||
}
|
||||
if (parsed.outOfBandRecord) {
|
||||
parsed.outOfBandRecord.forEach(record => {
|
||||
if (record.isStream) {
|
||||
this.log(record.type, record.content);
|
||||
} else {
|
||||
if (record.type == "exec") {
|
||||
this.emit("exec-async-output", parsed);
|
||||
if (record.asyncClass == "running")
|
||||
this.emit("running", parsed);
|
||||
else if (record.asyncClass == "stopped") {
|
||||
if (parsed.record("reason") == "breakpoint-hit")
|
||||
this.emit("breakpoint", parsed);
|
||||
else if (parsed.record("reason") == "end-stepping-range")
|
||||
this.emit("step-end", parsed);
|
||||
else if (parsed.record("reason") == "function-finished")
|
||||
this.emit("step-out-end", parsed);
|
||||
else
|
||||
this.emit("stopped", parsed);
|
||||
} else
|
||||
this.log("log", JSON.stringify(parsed));
|
||||
}
|
||||
}
|
||||
});
|
||||
handled = true;
|
||||
}
|
||||
if (parsed.token == undefined && parsed.resultRecords == undefined && parsed.outOfBandRecord.length == 0)
|
||||
handled = true;
|
||||
if (!handled)
|
||||
this.log("log", "Unhandled: " + JSON.stringify(parsed));
|
||||
});
|
||||
}
|
||||
|
||||
start(): Thenable<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.log("console", "Running executable");
|
||||
this.sendCommand("exec-run").then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
stop() {
|
||||
let proc = this.process;
|
||||
let to = setTimeout(() => {
|
||||
proc.kill();
|
||||
}, 2222);
|
||||
this.process.on("exit", function(code) {
|
||||
clearTimeout(to);
|
||||
});
|
||||
this.sendRaw("-gdb-exit");
|
||||
}
|
||||
|
||||
interrupt(): Thenable<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-interrupt").then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "done");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
continue(): Thenable<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-continue").then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
next(): Thenable<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-next").then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
step(): Thenable<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-step").then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
stepOut(): Thenable<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("exec-finish").then((info) => {
|
||||
resolve(info.resultRecords.resultClass == "running");
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]> {
|
||||
let promisses = [];
|
||||
breakpoints.forEach(breakpoint => {
|
||||
promisses.push(this.addBreakPoint(breakpoint));
|
||||
});
|
||||
return Promise.all(promisses);
|
||||
}
|
||||
|
||||
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.breakpoints.has(breakpoint))
|
||||
return resolve(false);
|
||||
this.sendCommand("break-insert " + breakpoint.file + ":" + breakpoint.line).then((result) => {
|
||||
if (result.resultRecords.resultClass == "done") {
|
||||
let newBrk = {
|
||||
file: result.result("bkpt.file"),
|
||||
line: parseInt(result.result("bkpt.line"))
|
||||
};
|
||||
this.breakpoints.set(newBrk, parseInt(result.result("bkpt.number")));
|
||||
resolve([true, newBrk]);
|
||||
}
|
||||
else {
|
||||
resolve([false, null]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.breakpoints.has(breakpoint))
|
||||
return resolve(false);
|
||||
this.sendCommand("break-delete " + this.breakpoints.get(breakpoint)).then((result) => {
|
||||
if (result.resultRecords.resultClass == "done") {
|
||||
this.breakpoints.delete(breakpoint);
|
||||
resolve(true);
|
||||
}
|
||||
else resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
clearBreakPoints(): Thenable<any> {
|
||||
let promisses = [];
|
||||
let it = this.breakpoints.keys();
|
||||
let value;
|
||||
while (!(value = it.next()).done) {
|
||||
promisses.push(this.removeBreakPoint(value.value));
|
||||
}
|
||||
return Promise.all(promisses);
|
||||
}
|
||||
|
||||
getStack(maxLevels: number): Thenable<Stack[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let command = "stack-list-frames";
|
||||
if (maxLevels) {
|
||||
command += " 0 " + maxLevels;
|
||||
}
|
||||
this.sendCommand(command).then((result) => {
|
||||
let stack = result.result("stack");
|
||||
let ret: Stack[] = [];
|
||||
stack.forEach(element => {
|
||||
let level = MINode.valueOf(element, "@frame.level");
|
||||
let addr = MINode.valueOf(element, "@frame.addr");
|
||||
let func = MINode.valueOf(element, "@frame.func");
|
||||
let filename = MINode.valueOf(element, "@frame.file");
|
||||
let file = MINode.valueOf(element, "@frame.fullname");
|
||||
let line = parseInt(MINode.valueOf(element, "@frame.line"));
|
||||
let from = parseInt(MINode.valueOf(element, "@frame.from"));
|
||||
ret.push({
|
||||
address: addr,
|
||||
fileName: filename || "",
|
||||
file: file || from || "<unknown>",
|
||||
function: func,
|
||||
level: level,
|
||||
line: line
|
||||
});
|
||||
});
|
||||
resolve(ret);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getStackVariables(thread: number, frame: number): Thenable<[string, string][]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("stack-list-variables --thread " + thread + " --frame " + frame + " --simple-values").then((result) => {
|
||||
let variables = result.result("variables");
|
||||
let ret: [string, string][] = [];
|
||||
variables.forEach(element => {
|
||||
const key = MINode.valueOf(element, "name");
|
||||
const value = MINode.valueOf(element, "value");
|
||||
ret.push([key, value]);
|
||||
});
|
||||
resolve(ret);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
evalExpression(name: string): Thenable<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendCommand("data-evaluate-expression " + name).then((result) => {
|
||||
resolve(result);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
log(type: string, msg: string) {
|
||||
this.emit("msg", type, msg[msg.length - 1] == '\n' ? msg : (msg + "\n"));
|
||||
}
|
||||
|
||||
sendRaw(raw: string) {
|
||||
this.process.stdin.write(raw + "\n");
|
||||
}
|
||||
|
||||
sendCommand(command: string): Thenable<MINode> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.handlers[this.currentToken] = (node: MINode) => {
|
||||
if (node.resultRecords && node.resultRecords.resultClass == "error") {
|
||||
let msg = node.result("msg") || "Internal error";
|
||||
this.log("stderr", "Failed to run command `" + command + "`: " + msg);
|
||||
reject(msg);
|
||||
}
|
||||
else
|
||||
resolve(node);
|
||||
};
|
||||
this.process.stdin.write(this.currentToken + "-" + command + "\n");
|
||||
this.currentToken++;
|
||||
});
|
||||
}
|
||||
|
||||
isReady(): boolean {
|
||||
return !!this.process;
|
||||
}
|
||||
|
||||
private currentToken: number = 1;
|
||||
private handlers: { [index: number]: (info: MINode) => any } = {};
|
||||
private breakpoints: Map<Breakpoint, Number> = new Map();
|
||||
private buffer: string;
|
||||
private process: ChildProcess.ChildProcess;
|
||||
}
|
||||
262
src/backend/mi_parse.ts
Normal file
262
src/backend/mi_parse.ts
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
export interface MIInfo {
|
||||
token: number;
|
||||
outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[];
|
||||
resultRecords: { resultClass: string, results: [string, any][] };
|
||||
}
|
||||
|
||||
export class MINode implements MIInfo {
|
||||
token: number;
|
||||
outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[];
|
||||
resultRecords: { resultClass: string, results: [string, any][] };
|
||||
|
||||
constructor(token: number, info: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[], result: { resultClass: string, results: [string, any][] }) {
|
||||
this.token = token;
|
||||
this.outOfBandRecord = info;
|
||||
this.resultRecords = result;
|
||||
}
|
||||
|
||||
record(path: string): any {
|
||||
if (!this.outOfBandRecord)
|
||||
return undefined;
|
||||
return MINode.valueOf(this.outOfBandRecord[0].output, path);
|
||||
}
|
||||
|
||||
result(path: string): any {
|
||||
if (!this.resultRecords)
|
||||
return undefined;
|
||||
return MINode.valueOf(this.resultRecords.results, path);
|
||||
}
|
||||
|
||||
static valueOf(start: any, path: string): any {
|
||||
if (!start)
|
||||
return undefined;
|
||||
let pathRegex = /^\.?([a-zA-Z_\-][a-zA-Z0-9_\-]*)/;
|
||||
let indexRegex = /^\[(\d+)\](?:$|\.)/;
|
||||
path = path.trim();
|
||||
if (!path)
|
||||
return start;
|
||||
let current = start;
|
||||
do {
|
||||
let target = pathRegex.exec(path);
|
||||
if (target) {
|
||||
path = path.substr(target[0].length);
|
||||
if (current.length && typeof current != "string") {
|
||||
let found = [];
|
||||
for (let i = 0; i < current.length; i++) {
|
||||
let element = current[i];
|
||||
if (element[0] == target[1]) {
|
||||
found.push(element[1]);
|
||||
}
|
||||
}
|
||||
if (found.length > 1) {
|
||||
current = found;
|
||||
} else if (found.length == 1) {
|
||||
current = found[0];
|
||||
} else return undefined;
|
||||
} else return undefined;
|
||||
}
|
||||
else if (path[0] == '@') {
|
||||
current = [current];
|
||||
path = path.substr(1);
|
||||
}
|
||||
else {
|
||||
target = indexRegex.exec(path);
|
||||
if (target) {
|
||||
path = path.substr(target[0].length);
|
||||
let i = parseInt(target[1]);
|
||||
if (current.length && typeof current != "string" && i >= 0 && i < current.length) {
|
||||
current = current[i];
|
||||
} else if (i == 0) {
|
||||
} else return undefined;
|
||||
}
|
||||
else return undefined;
|
||||
}
|
||||
path = path.trim();
|
||||
} while (path);
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
const tokenRegex = /^[0-9]+/;
|
||||
const outOfBandRecordRegex = /^(?:([0-9]*)([\*\+\=])|([\~\@\&]))/;
|
||||
const resultRecordRegex = /^([0-9]*)\^(done|running|connected|error|exit)/;
|
||||
const newlineRegex = /^\r\n?/;
|
||||
const endRegex = /^\(gdb\)\r\n?/;
|
||||
const variableRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*)/;
|
||||
const asyncClassRegex = /^(.*?),/;
|
||||
|
||||
export function parseMI(output: string): MINode {
|
||||
/*
|
||||
output ==>
|
||||
(
|
||||
exec-async-output = [ token ] "*" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||
status-async-output = [ token ] "+" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||
notify-async-output = [ token ] "=" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||
console-stream-output = "~" c-string \n
|
||||
target-stream-output = "@" c-string \n
|
||||
log-stream-output = "&" c-string \n
|
||||
)*
|
||||
[
|
||||
[ token ] "^" ("done" | "running" | "connected" | "error" | "exit") ( "," variable "=" (const | tuple | list) )* \n
|
||||
]
|
||||
"(gdb)" \n
|
||||
*/
|
||||
|
||||
let token = undefined;
|
||||
let outOfBandRecord = [];
|
||||
let resultRecords = undefined;
|
||||
|
||||
let asyncRecordType = {
|
||||
"*": "exec",
|
||||
"+": "status",
|
||||
"=": "notify"
|
||||
};
|
||||
let streamRecordType = {
|
||||
"~": "console",
|
||||
"@": "target",
|
||||
"&": "log"
|
||||
};
|
||||
|
||||
let parseCString = () => {
|
||||
if (output[0] != '"')
|
||||
return "";
|
||||
let stringEnd = 1;
|
||||
let inString = true;
|
||||
let remaining = output.substr(1);
|
||||
let escaped = false;
|
||||
while (inString) {
|
||||
if (escaped)
|
||||
escaped = false;
|
||||
else if (remaining[0] == '\\')
|
||||
escaped = true;
|
||||
else if (remaining[0] == '"')
|
||||
inString = false;
|
||||
|
||||
remaining = remaining.substr(1);
|
||||
stringEnd++;
|
||||
}
|
||||
// hax
|
||||
let str = JSON.parse(output.substr(0, stringEnd));
|
||||
output = output.substr(stringEnd);
|
||||
return str;
|
||||
};
|
||||
|
||||
let parseValue, parseCommaResult, parseCommaValue, parseResult;
|
||||
|
||||
let parseTupleOrList = () => {
|
||||
if (output[0] != '{' && output[0] != '[')
|
||||
return undefined;
|
||||
let oldContent = output;
|
||||
let canBeValueList = output[0] == '[';
|
||||
output = output.substr(1);
|
||||
if (output[0] == '}' || output[0] == ']')
|
||||
return [];
|
||||
if (canBeValueList) {
|
||||
let value = parseValue();
|
||||
if (value) { // is value list
|
||||
let values = [];
|
||||
values.push(value);
|
||||
let remaining = output;
|
||||
while (value = parseCommaValue())
|
||||
values.push(value);
|
||||
output = output.substr(1); // ]
|
||||
return values;
|
||||
}
|
||||
}
|
||||
let result = parseResult();
|
||||
if (result) {
|
||||
let results = [];
|
||||
results.push(result);
|
||||
while (result = parseCommaResult())
|
||||
results.push(result);
|
||||
output = output.substr(1); // }
|
||||
return results;
|
||||
}
|
||||
output = (canBeValueList ? '[' : '{') + output;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
parseValue = () => {
|
||||
if (output[0] == '"')
|
||||
return parseCString();
|
||||
else if (output[0] == '{' || output[0] == '[')
|
||||
return parseTupleOrList();
|
||||
else
|
||||
return undefined;
|
||||
};
|
||||
|
||||
parseResult = () => {
|
||||
let variableMatch = variableRegex.exec(output);
|
||||
if (!variableMatch)
|
||||
return undefined;
|
||||
output = output.substr(variableMatch[0].length + 1);
|
||||
let variable = variableMatch[1];
|
||||
return [variable, parseValue()];
|
||||
};
|
||||
|
||||
parseCommaValue = () => {
|
||||
if (output[0] != ',')
|
||||
return undefined;
|
||||
output = output.substr(1);
|
||||
return parseValue();
|
||||
};
|
||||
|
||||
parseCommaResult = () => {
|
||||
if (output[0] != ',')
|
||||
return undefined;
|
||||
output = output.substr(1);
|
||||
return parseResult();
|
||||
};
|
||||
|
||||
let match = undefined;
|
||||
|
||||
while (match = outOfBandRecordRegex.exec(output)) {
|
||||
output = output.substr(match[0].length);
|
||||
if (match[1] && token === undefined) {
|
||||
token = parseInt(match[1]);
|
||||
}
|
||||
|
||||
if (match[2]) {
|
||||
let classMatch = asyncClassRegex.exec(output);
|
||||
output = output.substr(classMatch[1].length);
|
||||
let asyncRecord = {
|
||||
isStream: false,
|
||||
type: asyncRecordType[match[2]],
|
||||
asyncClass: classMatch[1],
|
||||
output: []
|
||||
};
|
||||
let result;
|
||||
while (result = parseCommaResult())
|
||||
asyncRecord.output.push(result);
|
||||
outOfBandRecord.push(asyncRecord);
|
||||
}
|
||||
else if (match[3]) {
|
||||
let streamRecord = {
|
||||
isStream: true,
|
||||
type: streamRecordType[match[3]],
|
||||
content: parseCString()
|
||||
};
|
||||
outOfBandRecord.push(streamRecord);
|
||||
}
|
||||
|
||||
output = output.replace(newlineRegex, "");
|
||||
}
|
||||
|
||||
if (match = resultRecordRegex.exec(output)) {
|
||||
output = output.substr(match[0].length);
|
||||
if (match[1] && token === undefined) {
|
||||
token = parseInt(match[1]);
|
||||
}
|
||||
resultRecords = {
|
||||
resultClass: match[2],
|
||||
results: []
|
||||
};
|
||||
let result;
|
||||
while (result = parseCommaResult())
|
||||
resultRecords.results.push(result);
|
||||
|
||||
output = output.replace(newlineRegex, "");
|
||||
}
|
||||
|
||||
return new MINode(token, outOfBandRecord || [], resultRecords);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue