pddl-workspace
Version:
512 lines • 21.9 kB
JavaScript
"use strict";
/* --------------------------------------------------------------------------------------------
* Copyright (c) Jan Dolejsi. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
Object.defineProperty(exports, "__esModule", { value: true });
exports.PddlWorkspace = exports.FileType = exports.Folder = void 0;
const vscode_uri_1 = require("vscode-uri");
const events_1 = require("events");
const path_1 = require("path");
const HappeningsParser_1 = require("./parser/HappeningsParser");
const FileInfo_1 = require("./FileInfo");
const PddlSyntaxTreeBuilder_1 = require("./parser/PddlSyntaxTreeBuilder");
const DocumentPositionResolver_1 = require("./DocumentPositionResolver");
const PddlPlanParser_1 = require("./parser/PddlPlanParser");
const language_1 = require("./language");
const index_1 = require("./parser/index");
const PlannerRegistrar_1 = require("./planner/PlannerRegistrar");
function lowerCaseEquals(first, second) {
if (first === null || first === undefined) {
return second === null || second === undefined;
}
else if (second === null || second === undefined) {
return first === null || first === undefined;
}
else {
return first.toLowerCase() === second.toLowerCase();
}
}
class Folder {
constructor(folderPath) {
this.folderPath = folderPath;
this.files = new Map();
this.folderUri = vscode_uri_1.URI.file(this.folderPath);
}
getFolderUri() {
return this.folderUri;
}
hasFile(fileUri) {
return this.files.has(fileUri.toString());
}
get(fileUri) {
return this.files.get(fileUri.toString());
}
add(fileInfo) {
if (fileInfo.fileUri.scheme !== "git") {
this.files.set(fileInfo.fileUri.toString(), fileInfo);
}
}
remove(fileInfo) {
return this.files.delete(fileInfo.fileUri.toString());
}
removeByUri(fileUri) {
return this.files.delete(fileUri.toString());
}
getProblemFileWithName(problemName) {
return Array.from(this.files.values())
.filter(value => value.isProblem())
.map(value => value)
.find(problemInfo => lowerCaseEquals(problemInfo.name, problemName));
}
getProblemFilesFor(domainInfo) {
return Array.from(this.files.values())
.filter(value => value.isProblem())
.map(f => f)
.filter(problemInfo => lowerCaseEquals(problemInfo.domainName, domainInfo.name));
}
getDomainFilesFor(problemInfo) {
return Array.from(this.files.values())
.filter(value => value.isDomain())
.map(value => value)
.filter(domainInfo => lowerCaseEquals(domainInfo.name, problemInfo.domainName));
}
}
exports.Folder = Folder;
/**
* Enumeration of file types. The types `File` and `Directory` can also be
* a symbolic links, in that case use `FileType.File | FileType.SymbolicLink` and
* `FileType.Directory | FileType.SymbolicLink`.
*/
var FileType;
(function (FileType) {
/**
* The file type is unknown.
*/
FileType[FileType["Unknown"] = 0] = "Unknown";
/**
* A regular file.
*/
FileType[FileType["File"] = 1] = "File";
/**
* A directory.
*/
FileType[FileType["Directory"] = 2] = "Directory";
/**
* A symbolic link to a file.
*/
FileType[FileType["SymbolicLink"] = 64] = "SymbolicLink";
})(FileType || (exports.FileType = FileType = {}));
function toFileNameTypeUri(folderUri, file) {
return {
fileName: file[0],
fileType: file[1],
fileUri: vscode_uri_1.URI.file((0, path_1.join)(folderUri.fsPath, file[0])),
};
}
class PddlWorkspace extends events_1.EventEmitter {
constructor(options) {
super();
this.folders = new Map();
this.defaultTimerDelayInSeconds = 1;
this.lastVersionUpdateEmitted = new Map();
/** Explicit associations between problem files and domain files. */
this.problemToDomainMap = new Map();
/** Explicit associations between plan files and problem files. */
this.planToProblemMap = new Map();
this._epsilon = options.epsilon;
this.fileLoader = options.fileLoader;
this.pddlFileParsers = [new index_1.PddlDomainParser(), new index_1.PddlProblemParser(options.context)];
this.plannerRegistrar = new PlannerRegistrar_1.PlannerRegistrar();
}
get epsilon() {
return this._epsilon;
}
set epsilon(value) {
this._epsilon = value;
}
getPlannerRegistrar() {
return this.plannerRegistrar;
}
addExtension(extension) {
if (extension.getPddlParsers) {
const parsers = extension.getPddlParsers();
if (parsers) {
this.addPddlFileParser(parsers);
}
}
if (extension.getPlannerProvider) {
const plannerProviders = extension.getPlannerProvider();
if (plannerProviders) {
plannerProviders
.forEach(provider => this.plannerRegistrar.registerPlannerProvider(provider.kind, provider));
}
}
}
addPddlFileParser(parsers) {
this.pddlFileParsers.unshift(...parsers);
if (parsers.length > 0) {
this.getAllFilesIf(fi => fi.getLanguage() === language_1.PddlLanguage.PDDL)
.forEach(fi => this.invalidateDiagnostics(fi, language_1.FileStatus.Dirty));
this.scheduleParsing();
}
}
static getFolderPath(documentUri) {
const documentPath = documentUri.fsPath;
return (0, path_1.dirname)(documentPath);
}
static getFileName(documentUri) {
const documentPath = documentUri.fsPath;
return (0, path_1.basename)(documentPath);
}
static getFileInfoName(fileInfo) {
return this.getFileName(fileInfo.fileUri);
}
async upsertAndParseFile(fileUri, language, fileVersion, fileText, positionResolver) {
let fileInfo = await this.upsertFile(fileUri, language, fileVersion, fileText, positionResolver);
if (fileInfo.getStatus() === language_1.FileStatus.Dirty) {
fileInfo = await this.reParseFile(fileInfo);
}
return fileInfo;
}
async upsertFile(fileUri, language, fileVersion, fileText, positionResolver, force = false) {
const folderPath = PddlWorkspace.getFolderPath(fileUri);
const folder = this.upsertFolder(folderPath);
let fileInfo = folder.get(fileUri);
if (fileInfo) {
if (fileInfo.update(fileVersion, fileText, force)) {
this.scheduleParsing();
}
}
else {
fileInfo = await this.insertFile(folder, fileUri, language, fileVersion, fileText, positionResolver);
// ensure the other files in the folder are up to date too
this.loadFolder(folder);
}
return fileInfo;
}
async insertFile(folder, fileUri, language, fileVersion, fileText, positionResolver) {
const fileInfo = await this.parseFile(fileUri, language, fileVersion, fileText, positionResolver);
folder.add(fileInfo);
if (fileInfo.isDomain()) {
this.markProblemsAsDirty(fileInfo);
}
else if (fileInfo.isProblem()) {
this.markPlansAsDirty(fileInfo);
}
this.emitIfNew(PddlWorkspace.UPDATED, fileInfo);
this.emitIfNew(PddlWorkspace.INSERTED, fileInfo);
return fileInfo;
}
async loadFolder(folder) {
const folderUri = folder.getFolderUri();
if (this.fileLoader) {
// parse eagerly all (up to a max) PDDL files in the folder that have not been parsed already
const files = await this.fileLoader.readDirectory(folderUri);
const filePromises = files
.map(file => toFileNameTypeUri(folderUri, file))
.filter(file => file.fileType === FileType.File
&& (0, path_1.extname)(file.fileName).toLowerCase() === '.' + language_1.PDDL
// avoid parsing the same file again
&& !folder.hasFile(file.fileUri)
// stop at a maximum to avoid freezing the UI in huge folders
&& folder.files.size < PddlWorkspace.MAX_FILES_PER_FOLDER)
.map(async (file) => {
var _a, _b;
const fileContent = new TextDecoder("utf-8").decode(await ((_a = this.fileLoader) === null || _a === void 0 ? void 0 : _a.readFile(file.fileUri)));
return fileContent && this.insertFile(folder, file.fileUri, (_b = (0, language_1.toLanguageFromId)(language_1.PDDL)) !== null && _b !== void 0 ? _b : language_1.PddlLanguage.PDDL, -1, fileContent, new DocumentPositionResolver_1.SimpleDocumentPositionResolver(fileContent));
});
Promise.all(filePromises);
}
}
invalidateDiagnostics(fileInfo, fileStatus) {
fileInfo.setStatus(fileStatus !== null && fileStatus !== void 0 ? fileStatus : language_1.FileStatus.Parsed);
this.emitIfNew(PddlWorkspace.UPDATED, fileInfo);
}
markProblemsAsDirty(domainInfo) {
this.getProblemFiles(domainInfo).forEach(problemInfo => {
this.invalidateDiagnostics(problemInfo);
this.markPlansAsDirty(problemInfo);
});
}
markPlansAsDirty(problemInfo) {
this.getPlanFiles(problemInfo).forEach(planInfo => this.invalidateDiagnostics(planInfo));
this.getHappeningsFiles(problemInfo).forEach(happeningsInfo => this.invalidateDiagnostics(happeningsInfo));
}
scheduleParsing() {
this.cancelScheduledParsing();
this.parsingTimeout = setTimeout(() => this.parseAllDirty(), this.defaultTimerDelayInSeconds * 1000);
}
cancelScheduledParsing() {
if (this.parsingTimeout) {
clearTimeout(this.parsingTimeout);
} // todo: use .refresh()
}
parseAllDirty() {
// find all dirty files
const dirtyFiles = this.getAllFilesIf(fileInfo => fileInfo.getStatus() === language_1.FileStatus.Dirty);
dirtyFiles.forEach(file => this.reParseFile(file));
}
async reParseFile(fileInfo) {
const folderPath = PddlWorkspace.getFolderPath(fileInfo.fileUri);
const folder = this.upsertFolder(folderPath);
folder.remove(fileInfo);
fileInfo = await this.parseFile(fileInfo.fileUri, fileInfo.getLanguage(), fileInfo.getVersion(), fileInfo.getText(), fileInfo.getDocumentPositionResolver());
folder.add(fileInfo);
this.emitIfNew(PddlWorkspace.UPDATED, fileInfo);
return fileInfo;
}
/**
* Emit event, unless it is stale
* @param fileInfo file concerned
*/
emitIfNew(symbol, fileInfo) {
if (symbol === PddlWorkspace.UPDATED) {
const lastVersion = this.lastVersionUpdateEmitted.get(fileInfo.fileUri.toString());
if (lastVersion !== undefined && fileInfo.getVersion() <= lastVersion) {
return;
}
else {
this.lastVersionUpdateEmitted.set(fileInfo.fileUri.toString(), fileInfo.getVersion());
}
}
this.emit(symbol, fileInfo);
}
async parseFile(fileUri, language, fileVersion, fileText, positionResolver) {
if (language === language_1.PddlLanguage.PDDL) {
const parser = new PddlSyntaxTreeBuilder_1.PddlSyntaxTreeBuilder(fileText);
const syntaxTree = parser.getTree();
for (const pddlParser of this.pddlFileParsers) {
const pddlFile = await pddlParser.tryParse(fileUri, fileVersion, fileText, syntaxTree, positionResolver);
if (pddlFile) {
this.appendOffendingTokenToParsingProblems(pddlFile, parser, positionResolver);
return pddlFile;
}
}
const unknownFile = new FileInfo_1.UnknownFileInfo(fileUri, fileVersion, positionResolver);
unknownFile.setText(fileText);
return unknownFile;
}
else if (language === language_1.PddlLanguage.PLAN) {
return new PddlPlanParser_1.PddlPlanParser().parseText(fileText, this.epsilon, fileUri, fileVersion, positionResolver);
}
else if (language === language_1.PddlLanguage.HAPPENINGS) {
return new HappeningsParser_1.HappeningsParser().parseHappenings(fileUri, fileVersion, fileText, this.epsilon, positionResolver);
}
else {
throw Error("Unknown language: " + language);
}
}
appendOffendingTokenToParsingProblems(fileInfo, parser, positionResolver) {
fileInfo.addProblems(parser.getOffendingTokens().map(token => {
const offendingPosition = positionResolver.resolveToPosition(token.getStart());
return new FileInfo_1.ParsingProblem(`Unexpected token: ${token.toString()}`, "error", DocumentPositionResolver_1.PddlRange.createSingleCharacterRange({ line: offendingPosition.line, character: offendingPosition.character }));
}));
}
async upsertAndParseFolder(folderUri) {
const folderPath = folderUri.fsPath;
const folder = this.folders.get(folderPath);
if (folder) {
return folder;
}
const newFolder = new Folder(folderPath);
this.folders.set(folderPath, newFolder);
this.loadFolder(newFolder);
return newFolder;
}
getFolder(folderUri) {
return this.folders.get(folderUri.fsPath);
}
upsertFolder(folderPath) {
let folder;
if (!this.folders.has(folderPath)) {
folder = new Folder(folderPath);
this.folders.set(folderPath, folder);
}
else {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
folder = this.folders.get(folderPath);
}
return folder;
}
removeFile(documentUri, options) {
if (this.hasExplicitAssociations(documentUri)) {
if (!options.removeAllReferences) {
return false;
}
}
// todo: remove the explicit associations
const folderPath = PddlWorkspace.getFolderPath(documentUri);
if (this.folders.has(folderPath)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const folder = this.folders.get(folderPath);
if (folder.hasFile(documentUri)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const documentInfo = folder.get(documentUri);
this.emitIfNew(PddlWorkspace.REMOVING, documentInfo);
return folder.remove(documentInfo);
}
}
return false;
}
hasExplicitAssociations(documentUri) {
return this.problemToDomainMap.has(documentUri.toString()) || [...this.problemToDomainMap.values()].includes(documentUri.toString())
|| this.planToProblemMap.has(documentUri.toString()) || [...this.planToProblemMap.values()].includes(documentUri.toString());
}
getFileInfo(fileUri) {
const folderPath = PddlWorkspace.getFolderPath(fileUri);
if (this.folders.has(folderPath)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const folder = this.folders.get(folderPath);
const fileInfo = folder.get(fileUri);
return fileInfo; // or null if the file did not exist in the folder
}
// folder does not exist
return undefined;
}
getProblemFiles(domainInfo) {
var _a;
const folder = this.folders.get(PddlWorkspace.getFolderPath(domainInfo.fileUri));
// find problem files in the same folder that match the domain name
const problemFiles = (_a = folder === null || folder === void 0 ? void 0 : folder.getProblemFilesFor(domainInfo)) !== null && _a !== void 0 ? _a : [];
return problemFiles;
}
getPlanFiles(problemInfo) {
const folder = this.folders.get(PddlWorkspace.getFolderPath(problemInfo.fileUri));
if (folder === undefined) {
return [];
}
// find plan files in the same folder that match the domain and problem names
return Array.from(folder.files.values())
.filter(f => f.isPlan())
.map(f => f)
.filter(p => lowerCaseEquals(p.problemName, problemInfo.name)
&& lowerCaseEquals(p.domainName, problemInfo.domainName));
}
getHappeningsFiles(problemInfo) {
const folder = this.folders.get(PddlWorkspace.getFolderPath(problemInfo.fileUri));
if (folder === undefined) {
return [];
}
// find happenings files in the same folder that match the domain and problem names
return Array.from(folder.files.values())
.filter(f => f.isHappenings())
.map(f => f)
.filter(p => lowerCaseEquals(p.problemName, problemInfo.name)
&& lowerCaseEquals(p.domainName, problemInfo.domainName));
}
getAllFilesIf(predicate) {
const selectedFiles = new Array();
this.folders.forEach(folder => {
folder.files.forEach((fileInfo) => {
if (predicate.apply(this, [fileInfo])) {
selectedFiles.push(fileInfo);
}
});
});
return selectedFiles;
}
getAllFiles() {
const selectedFiles = new Array();
this.folders.forEach(folder => {
folder.files.forEach((fileInfo) => {
selectedFiles.push(fileInfo);
});
});
return selectedFiles;
}
/**
* Finds a corresponding domain file
* @param fileInfo a PDDL file info
* @returns corresponding domain file if fileInfo is a problem file,
* or `fileInfo` itself if the `fileInfo` is a domain file, or `null` otherwise.
*/
asDomain(fileInfo) {
if (fileInfo.isDomain()) {
return fileInfo;
}
else if (fileInfo.isProblem()) {
return this.getDomainFileFor(fileInfo);
}
else if (fileInfo.isPlan()) {
const problemFile1 = this.getProblemFileForPlan(fileInfo);
return problemFile1 && this.getDomainFileFor(problemFile1);
}
else if (fileInfo.isHappenings()) {
const problemFile2 = this.getProblemFileForHappenings(fileInfo);
return problemFile2 && this.getDomainFileFor(problemFile2);
}
else {
return undefined;
}
}
associateProblemToDomain(problemInfo, domainInfo) {
this.problemToDomainMap.set(problemInfo.fileUri.toString(), domainInfo.fileUri.toString());
}
/**
* Finds the matching domain files.
* @param problemFile problem file info
* @returns matching domain files (zero, one or many)
*/
getDomainFilesFor(problemFile) {
const key = problemFile.fileUri.toString();
// does an explicit association exist?
if (this.problemToDomainMap.has(key)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const domainFileUri = vscode_uri_1.URI.parse(this.problemToDomainMap.get(key));
const associatedDomain = this.getFileInfo(domainFileUri);
return associatedDomain ? [associatedDomain] : [];
}
else {
const folder = this.folders.get(PddlWorkspace.getFolderPath(problemFile.fileUri));
if (!folder) {
return [];
}
// find domain files in the same folder that match the problem's domain name
const domainFiles = folder.getDomainFilesFor(problemFile);
return domainFiles;
}
}
/**
* Finds the matching domain file in the same folder.
* @param problemFile problem file info
* @returns matching domain file, if exactly one exists in the same folder. `null` otherwise
*/
getDomainFileFor(problemFile) {
// find domain files in the same folder that match the problem's domain name
const domainFiles = this.getDomainFilesFor(problemFile);
return domainFiles.length === 1 ? domainFiles[0] : undefined;
}
associatePlanToProblem(planUri, problemFileInfo) {
this.planToProblemMap.set(planUri.toString(), problemFileInfo.fileUri.toString());
}
getProblemFileForPlan(planInfo) {
const key = planInfo.fileUri.toString();
// does an explicit association exist?
if (this.planToProblemMap.has(key)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const problemFileUri = vscode_uri_1.URI.parse(this.planToProblemMap.get(key));
return this.getFileInfo(problemFileUri);
}
else {
const folder = this.getFolderOf(planInfo);
if (!folder) {
return undefined;
}
return folder.getProblemFileWithName(planInfo.problemName);
}
}
getProblemFileForHappenings(happeningsInfo) {
var _a;
return (_a = this.getFolderOf(happeningsInfo)) === null || _a === void 0 ? void 0 : _a.getProblemFileWithName(happeningsInfo.problemName);
}
getFolderOf(fileInfo) {
return this.folders.get(PddlWorkspace.getFolderPath(fileInfo.fileUri));
}
}
exports.PddlWorkspace = PddlWorkspace;
PddlWorkspace.INSERTED = Symbol("INSERTED");
PddlWorkspace.UPDATED = Symbol("UPDATED");
PddlWorkspace.REMOVING = Symbol("REMOVING");
PddlWorkspace.MAX_FILES_PER_FOLDER = 30;
//# sourceMappingURL=PddlWorkspace.js.map