UNPKG

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
/* * Copyright (c) Jan Dolejsi 2022. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. */ 'use strict'; 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