ai-planning-val
Version:
Javascript/typescript wrapper for VAL (AI Planning plan validation and evaluation tools from KCL Planning department and the planning community around the ICAPS conference).
277 lines • 13.1 kB
JavaScript
/*
* Copyright (c) Jan Dolejsi 2022. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PddlLocation = exports.DiagnosticRelatedInformation = exports.PlanProblem = exports.PlanValidationOutcome = exports.PlanValidator = void 0;
const pddl_workspace_1 = require("pddl-workspace");
const process = __importStar(require("child_process"));
const valUtils_1 = require("./valUtils");
const DOMAIN_PREFIX = "domain";
const PROBLEM_PREFIX = "problem";
const PLAN_PREFIX = "plan";
/**
* Plan validating using the VAL validator executable.
*/
class PlanValidator {
constructor(outputCallback) {
this.outputCallback = outputCallback;
}
validate(domain, problem, plan, options) {
return __awaiter(this, void 0, void 0, function* () {
// copy editor content to temp files to avoid using out-of-date content on disk
const domainFilePath = yield valUtils_1.Util.toPddlFile(domain.getCompiledText(), { prefix: DOMAIN_PREFIX });
const problemFilePath = yield valUtils_1.Util.toPddlFile(problem.getCompiledText(), { prefix: PROBLEM_PREFIX });
const planFilePath = yield valUtils_1.Util.toPddlFile(plan.getText(), { prefix: PLAN_PREFIX });
const args = ['-v',];
if (options.epsilon) {
args.push('-t', options.epsilon.toString());
}
args.push(domainFilePath, problemFilePath, planFilePath);
this.outputCallback(options.validatePath + ' ' + args.join(' '));
return yield this.runProcess(options.validatePath, args, options.cwd, plan);
});
}
runProcess(exePath, args, cwd, planInfo) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const child = process.spawn(exePath, args, { cwd: cwd });
let outputString = '';
let errString = '';
child.stdout.on('data', output => {
const outputTextFragment = output.toString("utf8");
this.outputCallback('Error:/n' + outputTextFragment);
outputString += outputTextFragment;
});
child.stderr.on('data', errOutput => {
const errTextFragment = errOutput.toString("utf8");
this.outputCallback(errTextFragment);
errString += errTextFragment;
});
child.on("error", error => {
this.outputCallback(`Error: name=${error.name}, message=${error.message}`);
if (!child.killed) {
reject(error);
}
});
child.on("close", (code, signal) => {
if (code !== 0) {
console.warn(`${exePath} exit code: ${code}, signal: ${signal}.`);
this.outputCallback(`Exit code: ${code}`);
}
const outcome = this.analyzeOutput(planInfo, errString, undefined, outputString);
resolve(outcome);
});
});
});
}
analyzeOutput(planInfo, stderr, error, output) {
if (error) {
return PlanValidationOutcome.failed(planInfo, error);
}
if (output.match("Plan failed to execute") || output.match("Goal not satisfied")) {
const failurePattern = /Checking next happening \(time (\d+.\d+)\)/g;
let result;
let timeStamp = -1;
let remainingOutputIndex = 0;
while ((result = failurePattern.exec(output)) !== null) {
timeStamp = parseFloat(result[1]);
remainingOutputIndex = result.index + result[0].length + 1;
}
const match = output.match(/Plan Repair Advice:([\s\S]+)Failed plans:/);
if (match) {
return PlanValidationOutcome.failedAtTime(planInfo, timeStamp, match[1].trim().split('\n'));
}
else {
const errorOutput = output.substr(remainingOutputIndex).trim().split('\n')[0];
if (errorOutput) {
return PlanValidationOutcome.failedAtTime(planInfo, timeStamp, [errorOutput], {
severity: "error", showMoreInfoHint: true
});
}
else {
return PlanValidationOutcome.failedAtTime(planInfo, timeStamp, [
"Unidentified error. Run the 'PDDL: Validate plan' command for more info."
], { showMoreInfoHint: false });
}
}
}
const warnings = [];
const warningPattern = /Checking next happening \(time (\d+.\d+)\)\s*\nWARNING:([^\n]+)\n/g;
let warningMatch;
while ((warningMatch = warningPattern.exec(output)) !== null) {
const timeStamp = parseFloat(warningMatch[1]);
const warning = warningMatch[2];
warnings.push(...PlanValidationOutcome.createDiagnostics(planInfo, timeStamp, [warning]));
}
if (output.match("Bad plan description!")) {
return PlanValidationOutcome.invalidPlanDescription(planInfo);
}
else if (output.match("Plan valid")) {
return PlanValidationOutcome.validWithDiagnostics(planInfo, warnings);
}
if (stderr === null || stderr === void 0 ? void 0 : stderr.trim()) {
return PlanValidationOutcome.otherError(planInfo, stderr.trim());
}
return PlanValidationOutcome.unknown(planInfo);
}
}
exports.PlanValidator = PlanValidator;
class PlanValidationOutcome {
constructor(planInfo, diagnostics, error) {
this.planInfo = planInfo;
this.diagnostics = diagnostics;
this.error = error;
}
getPlanProblems() {
return this.diagnostics;
}
getError() {
return this.error;
}
static goalNotAttained(planInfo) {
var _a, _b;
const errorLine = planInfo.getSteps().length > 0 ? ((_b = (_a = planInfo.getSteps().slice(-1).pop()) === null || _a === void 0 ? void 0 : _a.lineIndex) !== null && _b !== void 0 ? _b : -1) + 1 : 0;
const error = "Plan does not reach the goal.";
const diagnostics = [createDiagnostic(errorLine, 0, error, "warning")];
return new PlanValidationOutcome(planInfo, diagnostics, error);
}
/**
* Creates validation outcomes for invalid plan i.e. plans that do not parse or do not correspond to the domain/problem file.
*/
static invalidPlanDescription(planInfo) {
const error = "Invalid plan description.";
const diagnostics = [createDiagnostic(0, 0, error, "error")];
return new PlanValidationOutcome(planInfo, diagnostics, error);
}
/**
* Creates validation outcomes for valid plan, which does not reach the goal.
*/
static valid(planInfo) {
return PlanValidationOutcome.validWithDiagnostics(planInfo, []);
}
/**
* Creates validation outcomes for valid plan, which does not reach the goal.
*/
static validWithDiagnostics(planInfo, diagnostics) {
return new PlanValidationOutcome(planInfo, diagnostics, undefined);
}
static failed(planInfo, error) {
const message = "Validate tool failed. " + error.message;
const diagnostic = createDiagnostic(0, 0, message, "error");
// todo: here is where we could set the diagnostic.code
return new PlanValidationOutcome(planInfo, [diagnostic], message);
}
static createDiagnostics(planInfo, timeStamp, repairHints, options) {
let errorLine = 0;
const stepAtTimeStamp = planInfo.getSteps()
.find(step => pddl_workspace_1.PlanStep.equalsWithin(step.getStartTime(), timeStamp, 1e-4));
if (stepAtTimeStamp && stepAtTimeStamp.lineIndex !== undefined) {
errorLine = stepAtTimeStamp.lineIndex;
}
const range = pddl_workspace_1.PddlRange.createFullLineRange(errorLine);
return repairHints.map(hint => PlanValidationOutcome.createDiagnostic(planInfo, range, hint, options));
}
static createDiagnostic(planInfo, range, message, options) {
var _a;
const diagnostic = new PlanProblem(range, message.trim(), (_a = options === null || options === void 0 ? void 0 : options.severity) !== null && _a !== void 0 ? _a : "warning");
if (options === null || options === void 0 ? void 0 : options.showMoreInfoHint) {
diagnostic.relatedInformation = [
new DiagnosticRelatedInformation(new PddlLocation(planInfo.fileUri, range), "Run the 'PDDL: Validate plan' command for more info.")
];
}
return diagnostic;
}
static failedAtTime(planInfo, timeStamp, repairHints, options) {
const diagnostics = PlanValidationOutcome.createDiagnostics(planInfo, timeStamp, repairHints, options);
return new PlanValidationOutcome(planInfo, diagnostics);
}
static otherError(planInfo, error) {
const diagnostics = [new PlanProblem(pddl_workspace_1.PddlRange.createFullLineRange(0), `${error}. Run the 'PDDL: Validate plan' command for more information.`, "error")];
return new PlanValidationOutcome(planInfo, diagnostics, error);
}
static unknown(planInfo) {
const diagnostics = [new PlanProblem(pddl_workspace_1.PddlRange.createFullLineRange(0), "Unknown error. Run the 'PDDL: Validate plan' command for more information.", "warning")];
return new PlanValidationOutcome(planInfo, diagnostics, "Unknown error.");
}
}
exports.PlanValidationOutcome = PlanValidationOutcome;
class PlanProblem extends pddl_workspace_1.ParsingProblem {
constructor(range, problem, severity) {
super(problem, severity, range);
this.relatedInformation = [];
}
}
exports.PlanProblem = PlanProblem;
function createDiagnostic(errorLine, errorColumn, error, severity) {
return new PlanProblem(pddl_workspace_1.PddlRange.createSingleLineRange({ line: errorLine, start: errorColumn, length: 111 }), error, severity);
}
/**
* Represents a related message and source code location for a diagnostic. This should be
* used to point to code locations that cause or related to a diagnostics, e.g. when duplicating
* a symbol in a scope.
*/
class DiagnosticRelatedInformation {
/**
* Creates a new related diagnostic information object.
*
* @param location The location.
* @param message The message.
*/
constructor(location, message) {
this.location = location;
this.message = message;
}
}
exports.DiagnosticRelatedInformation = DiagnosticRelatedInformation;
/**
* Represents a location inside a resource, such as a line
* inside a text file.
*/
class PddlLocation {
/**
* Creates a new location object.
*
* @param uri The resource identifier.
* @param rangeOrPosition The range or position. Positions will be converted to an empty range.
*/
constructor(uri, rangeOrPosition) {
this.uri = uri;
this.rangeOrPosition = rangeOrPosition;
}
}
exports.PddlLocation = PddlLocation;
//# sourceMappingURL=PlanValidator.js.map