Renames DataSource to EvaluationWatchService and DataExtractionProvider to Evaluator.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
|
||||
export class ComposedEvaluationEngine implements EvaluationEngine {
|
||||
constructor(public readonly engines: EvaluationEngine[]) {}
|
||||
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
for (const f of this.engines) {
|
||||
const evaluator = f.createEvaluator(session);
|
||||
if (evaluator) {
|
||||
return evaluator;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
|
||||
import { Config, DebugAdapterConfig } from "../../Config";
|
||||
import { GenericEvaluator } from "./GenericEvaluationEngine";
|
||||
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class ConfiguredEvaluationEngine implements EvaluationEngine {
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
const config = this.config.getDebugAdapterConfig(session.session.type);
|
||||
if (!config) {
|
||||
return undefined;
|
||||
}
|
||||
return new ConfiguredEvaluator(session, config);
|
||||
}
|
||||
}
|
||||
|
||||
class ConfiguredEvaluator extends GenericEvaluator {
|
||||
constructor(
|
||||
session: VsCodeDebugSession,
|
||||
private readonly config: DebugAdapterConfig
|
||||
) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
protected getContext() {
|
||||
return this.config.context;
|
||||
}
|
||||
|
||||
protected getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
}: {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
}) {
|
||||
return this.config.getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
|
||||
export interface EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined;
|
||||
}
|
||||
|
||||
export interface Evaluator {
|
||||
evaluate(
|
||||
args: EvaluationArgs
|
||||
): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
>;
|
||||
}
|
||||
|
||||
export interface EvaluationArgs {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
frameId: number | undefined;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import {
|
||||
EvaluationEngine,
|
||||
Evaluator,
|
||||
EvaluationArgs,
|
||||
} from "./EvaluationEngine";
|
||||
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class GenericEvaluationEngine implements EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
return new GenericEvaluator(session);
|
||||
}
|
||||
}
|
||||
|
||||
export class GenericEvaluator implements Evaluator {
|
||||
constructor(private readonly session: VsCodeDebugSession) {}
|
||||
|
||||
public async evaluate({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
frameId,
|
||||
}: EvaluationArgs): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
const finalExpression = this.getFinalExpression({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
});
|
||||
let reply;
|
||||
try {
|
||||
reply = await this.session.evaluate({
|
||||
expression: finalExpression,
|
||||
frameId,
|
||||
context: this.getContext(),
|
||||
});
|
||||
} catch (error) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
kind: "list",
|
||||
items: [
|
||||
error.message,
|
||||
`Used debug adapter: ${this.session.session.configuration.type}`,
|
||||
{
|
||||
kind: "inlineList",
|
||||
items: [
|
||||
"Evaluated expression is",
|
||||
{ kind: "code", content: finalExpression },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return parseEvaluationResultFromGenericDebugAdapter(reply.result);
|
||||
}
|
||||
|
||||
protected getFinalExpression(args: {
|
||||
expression: string;
|
||||
preferredExtractorId: DataExtractorId | undefined;
|
||||
}): string {
|
||||
return args.expression;
|
||||
}
|
||||
|
||||
protected getContext(): "watch" | "repl" {
|
||||
return "watch";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import {
|
||||
getExpressionForDataExtractorApi,
|
||||
DataResult,
|
||||
ApiHasNotBeenInitializedCode,
|
||||
getExpressionToInitializeDataExtractorApi,
|
||||
DataExtractionResult,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import {
|
||||
EvaluationEngine,
|
||||
Evaluator,
|
||||
EvaluationArgs,
|
||||
} from "./EvaluationEngine";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class JsEvaluationEngine implements EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
const supportedDebugAdapters = [
|
||||
"node",
|
||||
"node2",
|
||||
"extensionHost",
|
||||
"chrome",
|
||||
];
|
||||
if (supportedDebugAdapters.indexOf(session.session.type) !== -1) {
|
||||
return new JsEvaluator(session);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class JsEvaluator implements Evaluator {
|
||||
constructor(private readonly session: VsCodeDebugSession) {}
|
||||
|
||||
public async evaluate({
|
||||
expression,
|
||||
preferredExtractorId,
|
||||
frameId,
|
||||
}: EvaluationArgs): Promise<
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage }
|
||||
> {
|
||||
while (true) {
|
||||
try {
|
||||
const preferredExtractorExpr = preferredExtractorId
|
||||
? `"${preferredExtractorId}"`
|
||||
: "undefined";
|
||||
|
||||
const body = `${getExpressionForDataExtractorApi()}.getData(
|
||||
e => (${expression}),
|
||||
expr => eval(expr),
|
||||
${preferredExtractorExpr}
|
||||
)`;
|
||||
|
||||
const wrappedExpr = `
|
||||
(() => {
|
||||
try {
|
||||
return ${body};
|
||||
} catch (e) {
|
||||
return JSON.stringify({
|
||||
kind: "Error",
|
||||
message: e.message,
|
||||
stack: e.stack
|
||||
});
|
||||
}
|
||||
})()
|
||||
`;
|
||||
|
||||
const reply = await this.session.evaluate({
|
||||
expression: wrappedExpr,
|
||||
frameId,
|
||||
});
|
||||
const resultStr = reply.result;
|
||||
const jsonData = resultStr.substr(1, resultStr.length - 2);
|
||||
const result = JSON.parse(jsonData) as DataResult;
|
||||
|
||||
if (result.kind === "NoExtractors") {
|
||||
throw new Error("No extractors");
|
||||
} else if (result.kind === "Error") {
|
||||
throw new Error(result.message);
|
||||
} else if (result.kind === "Data") {
|
||||
return {
|
||||
kind: "data",
|
||||
result: result.extractionResult,
|
||||
};
|
||||
} else {
|
||||
throw new Error("Invalid Data");
|
||||
}
|
||||
} catch (error) {
|
||||
const msg = error.message as string | undefined;
|
||||
if (msg && msg.includes(ApiHasNotBeenInitializedCode)) {
|
||||
if (await this.initializeApi(frameId)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "error",
|
||||
message: error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeApi(frameId: number | undefined): Promise<boolean> {
|
||||
try {
|
||||
// prefer existing is true, so that manually registered (possibly newer) extractors are not overwritten.
|
||||
const expression = `${getExpressionToInitializeDataExtractorApi()}.registerDefaultExtractors(true);`;
|
||||
|
||||
await this.session.evaluate({
|
||||
expression,
|
||||
frameId,
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from "./EvaluationEngine";
|
||||
|
||||
export { ComposedEvaluationEngine } from "./ComposedEvaluationEngine";
|
||||
export { JsEvaluationEngine } from "./JsEvaluationEngine";
|
||||
export { GenericEvaluationEngine } from "./GenericEvaluationEngine";
|
||||
export { ConfiguredEvaluationEngine } from "./ConfiguredEvaluationEngine";
|
||||
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
DataExtractionResult,
|
||||
isExtractedData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
|
||||
export function parseEvaluationResultFromGenericDebugAdapter(
|
||||
resultText: string
|
||||
):
|
||||
| { kind: "data"; result: DataExtractionResult }
|
||||
| { kind: "error"; message: FormattedMessage } {
|
||||
const jsonData = resultText.trim();
|
||||
|
||||
let resultObj;
|
||||
try {
|
||||
try {
|
||||
let jsonData2;
|
||||
// Remove optionally enclosing characters.
|
||||
if (
|
||||
isEnclosedWith(jsonData, '"') ||
|
||||
isEnclosedWith(jsonData, "'")
|
||||
) {
|
||||
// In case of JavaScript: `"{ "kind": { ... }, "text": "some\ntext" }"`
|
||||
jsonData2 = jsonData.substr(1, jsonData.length - 2);
|
||||
} else {
|
||||
// Just in case no quoting is done.
|
||||
jsonData2 = jsonData;
|
||||
}
|
||||
resultObj = parseJson(jsonData2);
|
||||
} catch (e) {
|
||||
// in case of C++: `"{ \"kind\": { ... }, \"text\": \"some\\ntext\" }"`
|
||||
const str = parseJson(jsonData);
|
||||
// str is now `{ "kind": { ... }, "text": "some\ntext" }"`
|
||||
resultObj = parseJson(str);
|
||||
// result is now { kind: { ... }, text: "some\ntext" }
|
||||
}
|
||||
|
||||
if (!isExtractedData(resultObj)) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: {
|
||||
kind: "list",
|
||||
items: [
|
||||
"Evaluation result does not match ExtractedData interface.",
|
||||
{
|
||||
kind: "inlineList",
|
||||
items: [
|
||||
"Evaluation result was:",
|
||||
{
|
||||
kind: "code",
|
||||
content: JSON.stringify(
|
||||
resultObj,
|
||||
undefined,
|
||||
4
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
return {
|
||||
kind: "error",
|
||||
message: e.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "data",
|
||||
result: {
|
||||
availableExtractors: [],
|
||||
usedExtractor: {
|
||||
id: "generic" as any,
|
||||
name: "Generic",
|
||||
priority: 1,
|
||||
},
|
||||
data: resultObj,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function isEnclosedWith(str: string, char: string): boolean {
|
||||
return str.startsWith(char) && str.endsWith(char);
|
||||
}
|
||||
|
||||
function parseJson(str: string) {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (error) {
|
||||
throw new FormattedError({
|
||||
kind: "list",
|
||||
items: [
|
||||
"Could not parse evaluation result as JSON:",
|
||||
error.message,
|
||||
{
|
||||
kind: "inlineList",
|
||||
items: [
|
||||
"Evaluation result was:",
|
||||
{
|
||||
kind: "code",
|
||||
content: str,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class FormattedError {
|
||||
constructor(public readonly message: FormattedMessage) {}
|
||||
}
|
||||
Reference in New Issue
Block a user