UNPKG

pddl-workspace

Version:
512 lines 21.9 kB
"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