Skip to content

Commit

Permalink
split scope computation & provider
Browse files Browse the repository at this point in the history
  • Loading branch information
ydaveluy committed Sep 23, 2024
1 parent 94c3815 commit ec1c398
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 194 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@

import type { AstNode, AstNodeDescription, AstNodeDescriptionProvider, AstReflection, IndexManager, LangiumCoreServices, LangiumDocument, LangiumDocuments, PrecomputedScopes, Reference, ReferenceInfo, Scope, ScopeComputation, ScopeProvider, Stream, URI } from 'langium';
import type { XsmpcatServices } from '../xsmpcat-module.js';
import type { AstNode, AstNodeDescription, AstNodeDescriptionProvider, LangiumCoreServices, LangiumDocument, PrecomputedScopes, ScopeComputation } from 'langium';
import * as ast from '../generated/ast.js';
import { AstUtils, Cancellation, DocumentCache, EMPTY_SCOPE, MultiMap, WorkspaceCache, interruptAndCheck, stream } from 'langium';
import * as ProjectUtils from '../utils/project-utils.js';
import type { XsmpTypeProvider } from './type-provider.js';
import { Cancellation, MultiMap, interruptAndCheck } from 'langium';
import * as XsmpUtils from '../utils/xsmp-utils.js';
import { VisibilityKind } from '../utils/visibility-kind.js';

Expand Down Expand Up @@ -166,190 +162,4 @@ export class XsmpcatScopeComputation implements ScopeComputation {
return { ...description, name: `${element.name}.${description.name}` };
}

}

export class XsmpcatScopeProvider implements ScopeProvider {
protected documents: LangiumDocuments;
protected readonly visibleUris: WorkspaceCache<URI, Set<string> | undefined>;
protected readonly reflection: AstReflection;
protected readonly indexManager: IndexManager;
protected readonly typeProvider: XsmpTypeProvider;
protected readonly globalScopeCache: WorkspaceCache<URI, Scope>;
protected readonly contexts: Set<Reference> = new Set<Reference>();
protected readonly precomputedCache: DocumentCache<AstNode, Map<string, AstNodeDescription>>;

constructor(services: XsmpcatServices) {
this.documents = services.shared.workspace.LangiumDocuments;
this.visibleUris = new WorkspaceCache<URI, Set<string> | undefined>(services.shared);
this.reflection = services.shared.AstReflection;
this.indexManager = services.shared.workspace.IndexManager;
this.typeProvider = services.shared.TypeProvider;
this.globalScopeCache = new WorkspaceCache<URI, Scope>(services.shared);
this.precomputedCache = new DocumentCache<AstNode, Map<string, AstNodeDescription>>(services.shared);
}

protected collectScopesFromNode(node: AstNode, scopes: Array<Map<string, AstNodeDescription>>,
document: LangiumDocument) {

const precomputed = this.getPrecomputedScope(node, document);
if (precomputed.size > 0) {
scopes.push(precomputed);
}
switch (node.$type) {
case ast.Model:
case ast.Service: {
const component = node as ast.Component;
if (component.base) {
this.collectScopesFromReference(component.base, scopes);
}
component.interface.forEach(i => this.collectScopesFromReference(i, scopes));
break;
}
case ast.Interface: {
(node as ast.Interface).base.forEach(i => this.collectScopesFromReference(i, scopes));
break;
}
case ast.Class:
case ast.Exception: {
const clazz = node as ast.Class;
if (clazz.base)
this.collectScopesFromReference(clazz.base, scopes);
break;
}
}
}
protected collectScopesFromReference(node: Reference, scopes: Array<Map<string, AstNodeDescription>>) {
// Check if the node is currently processed to avoid cyclic dependency
if (!this.contexts.has(node) && node.ref) {
this.contexts.add(node);
try {
this.collectScopesFromNode(node.ref, scopes, AstUtils.getDocument(node.ref));
}
finally {
// Remove the context
this.contexts.delete(node);
}
}
}

getScope(context: ReferenceInfo): Scope {
// Store the context
this.contexts.add(context.reference);
try {
return this.computeScope(context);
}
finally {
// Remove the context
this.contexts.delete(context.reference);
}
}

private computeScope(context: ReferenceInfo): Scope {
let parent: Scope;

const scopes: Array<Map<string, AstNodeDescription>> = [];

if (ast.DesignatedInitializer === context.container.$type && context.property === 'field') {
if (context.container.$container) {
const type = this.typeProvider.getType(context.container.$container);
switch (type?.$type) {
case ast.Structure:
case ast.Class:
case ast.Exception:
this.collectScopesFromNode(type, scopes, AstUtils.getDocument(type));
parent = EMPTY_SCOPE;
break;
default: return EMPTY_SCOPE;
}
}
else {
return EMPTY_SCOPE;
}
}
else {
let currentNode = context.container.$container;
const document = AstUtils.getDocument(context.container);
parent = this.getGlobalScope(document);
while (currentNode) {
this.collectScopesFromNode(currentNode, scopes, document);
currentNode = currentNode.$container;
}
}

for (let i = scopes.length - 1; i >= 0; i--) {
parent = new MapScope(scopes[i], parent);
}

return parent;
}

private getVisibleUris(uri: URI): Set<string> | undefined {
return this.visibleUris.get(uri, () => ProjectUtils.findVisibleUris(this.documents, uri));
}

/**
* Create a global scope filtered for the given referenceType and on visibles projects URIs
*/
protected getGlobalScope(document: LangiumDocument): Scope {
return this.globalScopeCache.get(document.uri, () => new GlobalScope(this.indexManager.allElements(undefined, this.getVisibleUris(document.uri))));
}

protected getPrecomputedScope(node: AstNode, document: LangiumDocument): Map<string, AstNodeDescription> {
return this.precomputedCache.get(document.uri, node, () => {
const precomputed = new Map<string, AstNodeDescription>();
if (document.precomputedScopes) {
for (const element of document.precomputedScopes.get(node)) {
precomputed.set(element.name, element);
}
}
return precomputed;
});
}
}

export class GlobalScope implements Scope {
readonly elements: Map<string, AstNodeDescription>;
constructor(elements: Stream<AstNodeDescription>) {
this.elements = new Map();
for (const element of elements) {
this.elements.set(element.name, element);

// Import elements from Smp and Attributes namespaces in global namespace
if (element.name.startsWith('Smp.')) {
const name = element.name.substring(4);
this.elements.set(name, { ...element, name });
}
else if (element.name.startsWith('Attributes.')) {
const name = element.name.substring(11);
this.elements.set(name, { ...element, name });
}
}
}

getElement(name: string): AstNodeDescription | undefined {
return this.elements.get(name);
}

getAllElements(): Stream<AstNodeDescription> {
return stream(this.elements.values());
}
}

export class MapScope implements Scope {
readonly elements: Map<string, AstNodeDescription>;
readonly outerScope: Scope;

constructor(elements: Map<string, AstNodeDescription>, outerScope: Scope) {
this.elements = elements;
this.outerScope = outerScope;
}

getElement(name: string): AstNodeDescription | undefined {
return this.elements.get(name) ?? this.outerScope.getElement(name);
}

getAllElements(): Stream<AstNodeDescription> {
return stream(this.elements.values()).concat(this.outerScope.getAllElements());
}

}
}
193 changes: 193 additions & 0 deletions src/language/references/xsmpcat-scope-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@

import type { AstNode, AstNodeDescription, AstReflection, IndexManager, LangiumDocument, LangiumDocuments, Reference, ReferenceInfo, Scope, ScopeProvider, Stream, URI } from 'langium';
import type { XsmpcatServices } from '../xsmpcat-module.js';
import * as ast from '../generated/ast.js';
import { AstUtils, DocumentCache, EMPTY_SCOPE, WorkspaceCache, stream } from 'langium';
import * as ProjectUtils from '../utils/project-utils.js';
import type { XsmpTypeProvider } from './type-provider.js';

export class XsmpcatScopeProvider implements ScopeProvider {
protected documents: LangiumDocuments;
protected readonly visibleUris: WorkspaceCache<URI, Set<string> | undefined>;
protected readonly reflection: AstReflection;
protected readonly indexManager: IndexManager;
protected readonly typeProvider: XsmpTypeProvider;
protected readonly globalScopeCache: WorkspaceCache<URI, Scope>;
protected readonly contexts: Set<Reference> = new Set<Reference>();
protected readonly precomputedCache: DocumentCache<AstNode, Map<string, AstNodeDescription>>;

constructor(services: XsmpcatServices) {
this.documents = services.shared.workspace.LangiumDocuments;
this.visibleUris = new WorkspaceCache<URI, Set<string> | undefined>(services.shared);
this.reflection = services.shared.AstReflection;
this.indexManager = services.shared.workspace.IndexManager;
this.typeProvider = services.shared.TypeProvider;
this.globalScopeCache = new WorkspaceCache<URI, Scope>(services.shared);
this.precomputedCache = new DocumentCache<AstNode, Map<string, AstNodeDescription>>(services.shared);
}

protected collectScopesFromNode(node: AstNode, scopes: Array<Map<string, AstNodeDescription>>,
document: LangiumDocument) {

const precomputed = this.getPrecomputedScope(node, document);
if (precomputed.size > 0) {
scopes.push(precomputed);
}
switch (node.$type) {
case ast.Model:
case ast.Service: {
const component = node as ast.Component;
if (component.base) {
this.collectScopesFromReference(component.base, scopes);
}
component.interface.forEach(i => this.collectScopesFromReference(i, scopes));
break;
}
case ast.Interface: {
(node as ast.Interface).base.forEach(i => this.collectScopesFromReference(i, scopes));
break;
}
case ast.Class:
case ast.Exception: {
const clazz = node as ast.Class;
if (clazz.base)
this.collectScopesFromReference(clazz.base, scopes);
break;
}
}
}
protected collectScopesFromReference(node: Reference, scopes: Array<Map<string, AstNodeDescription>>) {
// Check if the node is currently processed to avoid cyclic dependency
if (!this.contexts.has(node) && node.ref) {
this.contexts.add(node);
try {
this.collectScopesFromNode(node.ref, scopes, AstUtils.getDocument(node.ref));
}
finally {
// Remove the context
this.contexts.delete(node);
}
}
}

getScope(context: ReferenceInfo): Scope {
// Store the context
this.contexts.add(context.reference);
try {
return this.computeScope(context);
}
finally {
// Remove the context
this.contexts.delete(context.reference);
}
}

private computeScope(context: ReferenceInfo): Scope {
let parent: Scope;

const scopes: Array<Map<string, AstNodeDescription>> = [];

if (ast.DesignatedInitializer === context.container.$type && context.property === 'field') {
if (context.container.$container) {
const type = this.typeProvider.getType(context.container.$container);
switch (type?.$type) {
case ast.Structure:
case ast.Class:
case ast.Exception:
this.collectScopesFromNode(type, scopes, AstUtils.getDocument(type));
parent = EMPTY_SCOPE;
break;
default: return EMPTY_SCOPE;
}
}
else {
return EMPTY_SCOPE;
}
}
else {
let currentNode = context.container.$container;
const document = AstUtils.getDocument(context.container);
parent = this.getGlobalScope(document);
while (currentNode) {
this.collectScopesFromNode(currentNode, scopes, document);
currentNode = currentNode.$container;
}
}

for (let i = scopes.length - 1; i >= 0; i--) {
parent = new MapScope(scopes[i], parent);
}

return parent;
}

private getVisibleUris(uri: URI): Set<string> | undefined {
return this.visibleUris.get(uri, () => ProjectUtils.findVisibleUris(this.documents, uri));
}

/**
* Create a global scope filtered for the given referenceType and on visibles projects URIs
*/
protected getGlobalScope(document: LangiumDocument): Scope {
return this.globalScopeCache.get(document.uri, () => new GlobalScope(this.indexManager.allElements(undefined, this.getVisibleUris(document.uri))));
}

protected getPrecomputedScope(node: AstNode, document: LangiumDocument): Map<string, AstNodeDescription> {
return this.precomputedCache.get(document.uri, node, () => {
const precomputed = new Map<string, AstNodeDescription>();
if (document.precomputedScopes) {
for (const element of document.precomputedScopes.get(node)) {
precomputed.set(element.name, element);
}
}
return precomputed;
});
}
}

export class GlobalScope implements Scope {
readonly elements: Map<string, AstNodeDescription>;
constructor(elements: Stream<AstNodeDescription>) {
this.elements = new Map();
for (const element of elements) {
this.elements.set(element.name, element);

// Import elements from Smp and Attributes namespaces in global namespace
if (element.name.startsWith('Smp.')) {
const name = element.name.substring(4);
this.elements.set(name, { ...element, name });
}
else if (element.name.startsWith('Attributes.')) {
const name = element.name.substring(11);
this.elements.set(name, { ...element, name });
}
}
}

getElement(name: string): AstNodeDescription | undefined {
return this.elements.get(name);
}

getAllElements(): Stream<AstNodeDescription> {
return stream(this.elements.values());
}
}

export class MapScope implements Scope {
readonly elements: Map<string, AstNodeDescription>;
readonly outerScope: Scope;

constructor(elements: Map<string, AstNodeDescription>, outerScope: Scope) {
this.elements = elements;
this.outerScope = outerScope;
}

getElement(name: string): AstNodeDescription | undefined {
return this.elements.get(name) ?? this.outerScope.getElement(name);
}

getAllElements(): Stream<AstNodeDescription> {
return stream(this.elements.values()).concat(this.outerScope.getAllElements());
}

}
Loading

0 comments on commit ec1c398

Please sign in to comment.