Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
import * as path from "path";
import ASTNode from "./ast/ASTNode";
import ScadFile from "./ast/ScadFile";
import ASTPinpointer from "./ASTPinpointer";
import ASTPrinter from "./ASTPrinter";
import CodeFile from "./CodeFile";
import CodeLocation from "./CodeLocation";
import CodeSpan from "./CodeSpan";
import FormattingConfiguration from "./FormattingConfiguration";
import ParsingHelper from "./ParsingHelper";
import PreludeUtil from "./prelude/PreludeUtil";
import ASTScopePopulator from "./semantic/ASTScopePopulator";
import ASTSymbolLister, { SymbolKind } from "./semantic/ASTSymbolLister";
import CompletionUtil from "./semantic/CompletionUtil";
import IncludeResolver from "./semantic/IncludeResolver";
import { ScadFileWithScope } from "./semantic/nodesWithScopes";
import {
ResolvedLookupExpr,
ResolvedModuleInstantiationStmt
} from "./semantic/resolvedNodes";
import ScadFileProvider, {
WithExportedScopes
} from "./semantic/ScadFileProvider";
import Scope from "./semantic/Scope";
import SymbolResolver from "./semantic/SymbolResolver";
export class SolutionFile implements WithExportedScopes {
codeFile!: CodeFile;
ast: ASTNode|null = null;
dependencies!: SolutionFile[];
errors!: Error[];
includeResolver: IncludeResolver<SolutionFile>;
includedFiles!: SolutionFile[];
onlyOwnScope!: Scope;
constructor(public solutionManager: SolutionManager) {
this.includeResolver = new IncludeResolver(this.solutionManager);
}
async parseAndProcess() {
let [ast, errors] = ParsingHelper.parseFile(this.codeFile);
if (ast) {
this.ast = new ASTScopePopulator(new Scope()).populate(ast);
this.includedFiles = await this.includeResolver.resolveIncludes(
this.ast as ScadFile,
errors
);
const usedFiles = await this.includeResolver.resolveIncludes(
this.ast as ScadFile,
errors
);
this.dependencies = [...this.includedFiles, ...usedFiles];
this.onlyOwnScope = (this.ast as ScadFileWithScope).scope.copy();
(this.ast as ScadFileWithScope).scope.siblingScopes = [
...this.includedFiles.map((f) => f.getExportedScopes()).flat(),
...usedFiles.map((f) => f.getExportedScopes()).flat(),
PreludeUtil.preludeScope,
];
this.ast = this.ast.accept(new SymbolResolver(errors));
}
this.errors = errors.errors;
}
getCompletionsAtLocation(loc: CodeLocation) {
return CompletionUtil.getSymbolsAtLocation(this.ast!, loc);
}
getSymbols<SymType>(
makeSymbol: (
name: string,
kind: SymbolKind,
fullRange: CodeSpan,
nameRange: CodeSpan,
children: SymType[]
) => SymType
) {
const l = new ASTSymbolLister<SymType>(makeSymbol);
return l.doList(this.ast!);
}
getFormatted() {
return new ASTPrinter(new FormattingConfiguration()).visitScadFile(
this.ast as ScadFile
);
}
getExportedScopes(): Scope[] {
return [
this.onlyOwnScope,
...this.includedFiles.map((f) => f.getExportedScopes()).flat(),
];
}
getSymbolDeclaration(loc: CodeLocation) {
const pp = new ASTPinpointer(loc).doPinpoint(this.ast!);
if (
pp instanceof ResolvedLookupExpr ||
pp instanceof ResolvedModuleInstantiationStmt
) {
return pp.resolvedDeclaration;
}
return null;
}
getSymbolDeclarationLocation(loc: CodeLocation): CodeLocation | null {
const decl = this.getSymbolDeclaration(loc);
if (decl) {
return decl.tokens.name ? decl.tokens.name.span.start : null;
}
return null;
}
}
export default class SolutionManager implements ScadFileProvider<SolutionFile> {
openedFiles: Map<string, SolutionFile> = new Map();
allFiles: Map<string, SolutionFile> = new Map();
notReadyFiles: Map<string, Promise<SolutionFile>> = new Map();
/**
* Returns a registered solution file for a given path. It supports getting files which have not been fully processed yet.
* @param filePath
*/
async getFile(filePath: string) {
if (!path.isAbsolute(filePath)) {
throw new Error("Path must be absolute and normalized.");
}
let file = this.allFiles.get(filePath);
if (file) {
return file;
}
return await this.notReadyFiles.get(filePath);
}
async notifyNewFileOpened(filePath: string, contents: string) {
if (!path.isAbsolute(filePath)) {
throw new Error("Path must be absolute and normalized.");
}
const cFile = new CodeFile(filePath, contents);
this.openedFiles.set(filePath, await this.attachSolutionFile(cFile));
}
async notifyFileChanged(filePath: string, contents: string) {
if (!path.isAbsolute(filePath)) {
throw new Error("Path must be absolute and normalized.");
}
const cFile = new CodeFile(filePath, contents);
let sf = this.openedFiles.get(filePath);
if (!sf) {
if (this.notReadyFiles.has(filePath)) {
sf = await this.notReadyFiles.get(filePath) as SolutionFile;
} else {
throw new Error("No such file");
}
}
sf.codeFile = cFile;
await sf.parseAndProcess();
}
notifyFileClosed(filePath: string) {
this.openedFiles.delete(filePath);
this.garbageCollect();
}
protected async attachSolutionFile(codeFile: CodeFile) {
const solutionFile = new SolutionFile(this);
solutionFile.codeFile = codeFile;
try {
let resolve!: (s: SolutionFile) => void;
this.notReadyFiles.set(
codeFile.path,
new Promise<SolutionFile>((r) => (resolve = r))
);
await solutionFile.parseAndProcess();
resolve(solutionFile);
this.allFiles.set(codeFile.path, solutionFile);
return solutionFile;
} finally {
this.notReadyFiles.delete(codeFile.path);
}
}
/**
* Checks whether a file is already in the solution, and if not it loads it from disk.
* @param filePath The dependent-upon file.
*/
async provideScadFile(filePath: string) {
let f: SolutionFile | undefined = await this.getFile(filePath);
if (f) return f; // the file is already opened or refrenced by antoher
return await this.attachSolutionFile(await CodeFile.load(filePath));
}
/**
* Removes dependencies that aren't directly or indirectly referenced in any of the open files to free memory.
*/
protected garbageCollect() {
const gcMarked = new WeakMap<SolutionFile, boolean>();
function markRecursive(f: SolutionFile) {
gcMarked.set(f, true);
for (const dep of f.dependencies) {
if (!gcMarked.has(dep)) {
markRecursive(dep);
}
}
}
for (const [_, dep] of this.openedFiles) {
markRecursive(dep);
}
for (const [path, f] of this.allFiles) {
if (!gcMarked.has(f)) {
this.allFiles.delete(path);
}
}
}
}
2d56448eaThis file is pinned to the selected release. Download the original to open it in your design tool.