Renames DataSource to EvaluationWatchService and DataExtractionProvider to Evaluator.

This commit is contained in:
Henning Dieterichs
2020-03-24 15:37:31 +01:00
parent 2ea604d2b5
commit 6a71c23fdb
17 changed files with 79 additions and 96 deletions

View File

@@ -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;
}
}

View File

@@ -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,
});
}
}

View File

@@ -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;
}

View File

@@ -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";
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,6 @@
export * from "./EvaluationEngine";
export { ComposedEvaluationEngine } from "./ComposedEvaluationEngine";
export { JsEvaluationEngine } from "./JsEvaluationEngine";
export { GenericEvaluationEngine } from "./GenericEvaluationEngine";
export { ConfiguredEvaluationEngine } from "./ConfiguredEvaluationEngine";

View File

@@ -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) {}
}