Skip to content

Commit a08eee6

Browse files
authored
Merge pull request #24 from lstreckeisen/CMI-82-Document-Language-Server-Grammar-Issues
refactoring to make patterns clearer
2 parents ecdcc8b + 2f60f34 commit a08eee6

18 files changed

+257
-149
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ src/language/generated/
1010
syntaxes/
1111
.idea
1212
.DS_Store
13-
cml-ls/
13+
cml-ls/
14+
docs/*.png

docs/class_semantic_tokens.puml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
@startuml
2+
abstract class AbstractSemanticTokenProvider
3+
4+
class ContextMapperDslSemanticTokenProvider {
5+
# highlightElement(AstNode, SemanticTokenAcceptor)
6+
}
7+
8+
class SemanticTokenProviderRegistry {
9+
+ get(AstNode): ContextMapperSemanticTokenProvider<AstNode>
10+
}
11+
12+
interface ContextMapperSemanticTokenProvider {
13+
+ supports(AstNode): boolean
14+
+ highlight(T, SemanticTokenAcceptor)
15+
}
16+
17+
AbstractSemanticTokenProvider <|-- ContextMapperDslSemanticTokenProvider
18+
ContextMapperDslSemanticTokenProvider --> SemanticTokenProviderRegistry
19+
SemanticTokenProviderRegistry o-- "0..*" ContextMapperSemanticTokenProvider
20+
@enduml
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@startuml
2+
abstract class ValidationRegistry {
3+
+ register(ValidationChecks<T>, ThisParameterType)
4+
}
5+
6+
class ContextMapperDslValidationRegistry {
7+
}
8+
9+
class ContextMapperDslValidator {
10+
+ checkContextMappingModel(ContextMappingModel, ValidationAcceptor)
11+
+ checkValue(Value, ValidationAcceptor)
12+
}
13+
14+
class ContextMapperValidationProviderRegistry {
15+
+ get(AstNode): ContextMapperValidationProvider<AstNode>
16+
}
17+
18+
interface ContextMapperValidationProvider {
19+
+ supports(AstNode): boolean
20+
+ validate(T, ValidationAcceptor)
21+
}
22+
23+
ValidationRegistry <|-- ContextMapperDslValidationRegistry
24+
ContextMapperDslValidationRegistry --> ContextMapperDslValidator
25+
ContextMapperDslValidator --> ContextMapperValidationProviderRegistry
26+
ContextMapperValidationProviderRegistry o-- "0..*" ContextMapperValidationProvider
27+
@enduml

src/language/ContextMapperDslModule.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import {
88
type PartialLangiumServices
99
} from 'langium/lsp'
1010
import { ContextMapperDslGeneratedModule, ContextMapperDslGeneratedSharedModule } from './generated/module.js'
11-
import { ContextMapperDslValidator, registerValidationChecks } from './ContextMapperDslValidator.js'
11+
import { ContextMapperDslValidator } from './validation/ContextMapperDslValidator.js'
1212
import { ContextMapperDslSemanticTokenProvider } from './semantictokens/ContextMapperDslSemanticTokenProvider.js'
13+
import { SemanticTokenProviderRegistry } from './semantictokens/SemanticTokenProviderRegistry.js'
14+
import { ContextMapperDslValidationRegistry } from './validation/ContextMapperDslValidationRegistry.js'
15+
import { ContextMapperValidationProviderRegistry } from './validation/ContextMapperValidationProviderRegistry.js'
1316

1417
/**
1518
* Declaration of custom services - add your own service classes here.
@@ -34,12 +37,16 @@ export type ContextMapperDslServices = LangiumServices & ContextMapperDslAddedSe
3437

3538
type ModuleType = PartialLangiumServices & ContextMapperDslAddedServices;
3639

40+
const semanticTokenProviderRegistry = new SemanticTokenProviderRegistry()
41+
const validationProviderRegistry = new ContextMapperValidationProviderRegistry()
42+
3743
export const ContextMapperDslModule: Module<ContextMapperDslServices, ModuleType> = {
3844
validation: {
39-
ContextMapperDslValidator: () => new ContextMapperDslValidator()
45+
ContextMapperDslValidator: () => new ContextMapperDslValidator(validationProviderRegistry),
46+
ValidationRegistry: (services) => new ContextMapperDslValidationRegistry(services)
4047
},
4148
lsp: {
42-
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services)
49+
SemanticTokenProvider: (services) => new ContextMapperDslSemanticTokenProvider(services, semanticTokenProviderRegistry)
4350
}
4451
}
4552

@@ -72,7 +79,6 @@ export function createContextMapperDslServices (context: DefaultSharedModuleCont
7279
ContextMapperDslModule
7380
)
7481
shared.ServiceRegistry.register(ContextMapperDsl)
75-
registerValidationChecks(ContextMapperDsl.validation.ValidationRegistry, ContextMapperDsl.validation.ContextMapperDslValidator)
7682
if (!context.connection) {
7783
// We don't run inside a language server
7884
// Therefore, initialize the configuration provider instantly

src/language/ContextMapperDslValidator.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 12 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,21 @@
11
import { AstNode } from 'langium'
2-
import { AbstractSemanticTokenProvider, SemanticTokenAcceptor } from 'langium/lsp'
3-
import {
4-
ContextMappingModel,
5-
isContextMappingModel
6-
} from '../generated/ast.js'
7-
import { ContextMapSemanticTokenProvider } from './contextMap/ContextMapSemanticTokenProvider.js'
8-
import { SemanticTokenTypes } from 'vscode-languageserver-types'
9-
import { BoundedContextSemanticTokenProvider } from './boundedContext/BoundedContextSemanticTokenProvider.js'
10-
import { DomainSemanticTokenProvider } from './domain/DomainSemanticTokenProvider.js'
11-
import { AggregateSemanticTokenProvider } from './boundedContext/AggregateSemanticTokenProvider.js'
12-
import { RequirementsSemanticTokenProvider } from './requirements/RequirementsSemanticTokenProvider.js'
13-
import { ValueSemanticTokenProvider } from './vdad/ValueSemanticTokenProvider.js'
14-
import { ContextMapperSemanticTokenProvider } from './ContextMapperSemanticTokenProvider.js'
15-
import { SculptorModuleSemanticTokenProvider } from './boundedContext/SculptorModuleSemanticTokenProvider.js'
16-
import { RelationshipSemanticTokenProvider } from './contextMap/RelationshipSemanticTokenProvider.js'
17-
import { FeatureSemanticTokenProvider } from './requirements/FeatureSemanticTokenProvider.js'
18-
import { StoryValuationSemanticTokenProvider } from './requirements/StoryValuationSemanticTokenProvider.js'
19-
import { AbstractStakeholderSemanticTokenProvider } from './vdad/AbstractStakeholderSemanticTokenProvider.js'
20-
import { ActionSemanticTokenProvider } from './vdad/ActionSemanticTokenProvider.js'
21-
import { ConsequenceSemanticTokenProvider } from './vdad/ConsequenceSemanticTokenProvider.js'
22-
import { StakeholderSemanticTokenProvider } from './vdad/StakeholderSemanticTokenProvider.js'
23-
import { ValueClusterSemanticTokenProvider } from './vdad/ValueClusterSemanticTokenProvider.js'
24-
import { ValueElicitationSemanticTokenProvider } from './vdad/ValueElicitationSemanticTokenProvider.js'
25-
import { ValueEpicSemanticTokenProvider } from './vdad/ValueEpicSemanticTokenProvider.js'
26-
import { ValueNarrativeSemanticTokenProvider } from './vdad/ValueNarrativeSemanticTokenProvider.js'
27-
import { ValueRegisterSemanticTokenProvider } from './vdad/ValueRegisterSemanticTokenProvider.js'
28-
import { ValueWeightingSemanticTokenProvider } from './vdad/ValueWeightingSemanticTokenProvider.js'
2+
import { AbstractSemanticTokenProvider, LangiumServices, SemanticTokenAcceptor } from 'langium/lsp'
3+
import { SemanticTokenProviderRegistry } from './SemanticTokenProviderRegistry.js'
294

305
export class ContextMapperDslSemanticTokenProvider extends AbstractSemanticTokenProvider {
31-
private readonly semanticTokenProviders: ContextMapperSemanticTokenProvider<AstNode>[] = [
32-
new AggregateSemanticTokenProvider(),
33-
new BoundedContextSemanticTokenProvider(),
34-
new SculptorModuleSemanticTokenProvider(),
35-
new ContextMapSemanticTokenProvider(),
36-
new RelationshipSemanticTokenProvider(),
37-
new DomainSemanticTokenProvider(),
38-
new FeatureSemanticTokenProvider(),
39-
new RequirementsSemanticTokenProvider(),
40-
new StoryValuationSemanticTokenProvider(),
41-
new AbstractStakeholderSemanticTokenProvider(),
42-
new ActionSemanticTokenProvider(),
43-
new ConsequenceSemanticTokenProvider(),
44-
new StakeholderSemanticTokenProvider(),
45-
new ValueClusterSemanticTokenProvider(),
46-
new ValueElicitationSemanticTokenProvider(),
47-
new ValueEpicSemanticTokenProvider(),
48-
new ValueNarrativeSemanticTokenProvider(),
49-
new ValueRegisterSemanticTokenProvider(),
50-
new ValueSemanticTokenProvider(),
51-
new ValueWeightingSemanticTokenProvider()
52-
]
6+
private readonly semanticTokenProviderRegistry: SemanticTokenProviderRegistry
537

54-
protected override highlightElement (node: AstNode, acceptor: SemanticTokenAcceptor) {
55-
if (isContextMappingModel(node)) {
56-
if (node.$cstNode) {
57-
this.highlightComments(/\/\*[\s\S]*?\*\//g, node, acceptor)
58-
this.highlightComments(/\/\/[^\n\r]*/g, node, acceptor)
59-
}
60-
} else {
61-
for (const provider of this.semanticTokenProviders) {
62-
if (provider.supports(node)) {
63-
provider.highlight(node, acceptor)
64-
return
65-
}
66-
}
67-
68-
console.error('Uncaught node type', node.$type)
69-
}
8+
constructor (services: LangiumServices, registry: SemanticTokenProviderRegistry) {
9+
super(services)
10+
this.semanticTokenProviderRegistry = registry
7011
}
7112

72-
private highlightComments (regex: RegExp, node: ContextMappingModel, acceptor: SemanticTokenAcceptor) {
73-
if (node.$document == null) {
74-
throw new Error('Document not found')
75-
}
76-
const text = node.$document.textDocument.getText()
77-
for (const match of text.matchAll(regex)) {
78-
if (match?.index == null) {
79-
continue
80-
}
81-
const position = node.$document.textDocument.positionAt(match.index)
82-
acceptor({
83-
type: SemanticTokenTypes.comment,
84-
line: position.line,
85-
char: position.character,
86-
length: match[0].length
87-
})
13+
protected override highlightElement (node: AstNode, acceptor: SemanticTokenAcceptor) {
14+
const semanticTokenProvider = this.semanticTokenProviderRegistry.get(node)
15+
if (semanticTokenProvider) {
16+
semanticTokenProvider.highlight(node, acceptor)
17+
} else {
18+
console.error('Node type with no token provider', node.$type)
8819
}
8920
}
9021
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ContextMapperSemanticTokenProvider } from './ContextMapperSemanticTokenProvider.js'
2+
import { AstNode } from 'langium'
3+
import { AggregateSemanticTokenProvider } from './boundedContext/AggregateSemanticTokenProvider.js'
4+
import { BoundedContextSemanticTokenProvider } from './boundedContext/BoundedContextSemanticTokenProvider.js'
5+
import { SculptorModuleSemanticTokenProvider } from './boundedContext/SculptorModuleSemanticTokenProvider.js'
6+
import { ContextMapSemanticTokenProvider } from './contextMap/ContextMapSemanticTokenProvider.js'
7+
import { RelationshipSemanticTokenProvider } from './contextMap/RelationshipSemanticTokenProvider.js'
8+
import { DomainSemanticTokenProvider } from './domain/DomainSemanticTokenProvider.js'
9+
import { FeatureSemanticTokenProvider } from './requirements/FeatureSemanticTokenProvider.js'
10+
import { RequirementsSemanticTokenProvider } from './requirements/RequirementsSemanticTokenProvider.js'
11+
import { StoryValuationSemanticTokenProvider } from './requirements/StoryValuationSemanticTokenProvider.js'
12+
import { AbstractStakeholderSemanticTokenProvider } from './vdad/AbstractStakeholderSemanticTokenProvider.js'
13+
import { ActionSemanticTokenProvider } from './vdad/ActionSemanticTokenProvider.js'
14+
import { ConsequenceSemanticTokenProvider } from './vdad/ConsequenceSemanticTokenProvider.js'
15+
import { StakeholderSemanticTokenProvider } from './vdad/StakeholderSemanticTokenProvider.js'
16+
import { ValueClusterSemanticTokenProvider } from './vdad/ValueClusterSemanticTokenProvider.js'
17+
import { ValueElicitationSemanticTokenProvider } from './vdad/ValueElicitationSemanticTokenProvider.js'
18+
import { ValueEpicSemanticTokenProvider } from './vdad/ValueEpicSemanticTokenProvider.js'
19+
import { ValueNarrativeSemanticTokenProvider } from './vdad/ValueNarrativeSemanticTokenProvider.js'
20+
import { ValueRegisterSemanticTokenProvider } from './vdad/ValueRegisterSemanticTokenProvider.js'
21+
import { ValueSemanticTokenProvider } from './vdad/ValueSemanticTokenProvider.js'
22+
import { ValueWeightingSemanticTokenProvider } from './vdad/ValueWeightingSemanticTokenProvider.js'
23+
import { ContextMappingModelSemanticTokenProvider } from './model/ContextMappingModelSemanticTokenProvider.js'
24+
25+
export class SemanticTokenProviderRegistry {
26+
private readonly semanticTokenProviders: ContextMapperSemanticTokenProvider<AstNode>[] = [
27+
new AggregateSemanticTokenProvider(),
28+
new BoundedContextSemanticTokenProvider(),
29+
new SculptorModuleSemanticTokenProvider(),
30+
new ContextMapSemanticTokenProvider(),
31+
new RelationshipSemanticTokenProvider(),
32+
new DomainSemanticTokenProvider(),
33+
new FeatureSemanticTokenProvider(),
34+
new RequirementsSemanticTokenProvider(),
35+
new StoryValuationSemanticTokenProvider(),
36+
new AbstractStakeholderSemanticTokenProvider(),
37+
new ActionSemanticTokenProvider(),
38+
new ConsequenceSemanticTokenProvider(),
39+
new StakeholderSemanticTokenProvider(),
40+
new ValueClusterSemanticTokenProvider(),
41+
new ValueElicitationSemanticTokenProvider(),
42+
new ValueEpicSemanticTokenProvider(),
43+
new ValueNarrativeSemanticTokenProvider(),
44+
new ValueRegisterSemanticTokenProvider(),
45+
new ValueSemanticTokenProvider(),
46+
new ValueWeightingSemanticTokenProvider(),
47+
new ContextMappingModelSemanticTokenProvider()
48+
]
49+
50+
get (node: AstNode): ContextMapperSemanticTokenProvider<AstNode> | undefined {
51+
return this.semanticTokenProviders.find(provider => provider.supports(node))
52+
}
53+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ContextMapperSemanticTokenProvider } from '../ContextMapperSemanticTokenProvider.js'
2+
import { ContextMappingModel, isContextMappingModel } from '../../generated/ast.js'
3+
import { AstNode } from 'langium'
4+
import { SemanticTokenAcceptor } from 'langium/lsp'
5+
import { SemanticTokenTypes } from 'vscode-languageserver-types'
6+
7+
const ML_COMMENT_REGEX = /\/\*[\s\S]*?\*\//g
8+
const SL_COMMENT_REGEX = /\/\/[^\n\r]*/g
9+
10+
export class ContextMappingModelSemanticTokenProvider implements ContextMapperSemanticTokenProvider<ContextMappingModel> {
11+
supports (node: AstNode): node is ContextMappingModel {
12+
return isContextMappingModel(node)
13+
}
14+
15+
highlight (node: ContextMappingModel, acceptor: SemanticTokenAcceptor): void {
16+
if (node.$cstNode) {
17+
this.highlightComments(ML_COMMENT_REGEX, node, acceptor)
18+
this.highlightComments(SL_COMMENT_REGEX, node, acceptor)
19+
}
20+
}
21+
22+
private highlightComments (regex: RegExp, node: ContextMappingModel, acceptor: SemanticTokenAcceptor) {
23+
if (node.$document == null) {
24+
throw new Error('Document not found')
25+
}
26+
const text = node.$document.textDocument.getText()
27+
for (const match of text.matchAll(regex)) {
28+
if (match?.index == null) {
29+
continue
30+
}
31+
const position = node.$document.textDocument.positionAt(match.index)
32+
acceptor({
33+
type: SemanticTokenTypes.comment,
34+
line: position.line,
35+
char: position.character,
36+
length: match[0].length
37+
})
38+
}
39+
}
40+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { type ValidationChecks, ValidationRegistry } from 'langium'
2+
import { ContextMapperDslServices } from '../ContextMapperDslModule.js'
3+
import type { ContextMapperDslAstType } from '../generated/ast.js'
4+
5+
export class ContextMapperDslValidationRegistry extends ValidationRegistry {
6+
constructor (services: ContextMapperDslServices) {
7+
super(services)
8+
const validator = services.validation.ContextMapperDslValidator
9+
const checks: ValidationChecks<ContextMapperDslAstType> = {
10+
ContextMappingModel: validator.checkContextMappingModel,
11+
Value: validator.checkValue
12+
}
13+
super.register(checks, validator)
14+
}
15+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { ValidationAcceptor } from 'langium'
2+
import type { ContextMappingModel, Value } from '../generated/ast.js'
3+
import { ContextMapperValidationProviderRegistry } from './ContextMapperValidationProviderRegistry.js'
4+
5+
export class ContextMapperDslValidator {
6+
private readonly _registry: ContextMapperValidationProviderRegistry
7+
8+
constructor (contextMapperValidationProviderRegistry: ContextMapperValidationProviderRegistry) {
9+
this._registry = contextMapperValidationProviderRegistry
10+
}
11+
12+
checkContextMappingModel (model: ContextMappingModel, acceptor: ValidationAcceptor) {
13+
this._registry.get(model)?.validate(model, acceptor)
14+
}
15+
16+
checkValue (value: Value, acceptor: ValidationAcceptor) {
17+
this._registry.get(value)?.validate(value, acceptor)
18+
}
19+
}

0 commit comments

Comments
 (0)