Skip to content

Commit 7a02166

Browse files
committed
Added ParserOptions to parseHelper
1 parent 10f216c commit 7a02166

File tree

4 files changed

+52
-32
lines changed

4 files changed

+52
-32
lines changed

packages/langium/src/parser/langium-parser.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ export abstract class AbstractLangiumParser implements BaseParser {
131131
}
132132
}
133133

134+
export interface ParserOptions {
135+
rule?: string
136+
}
137+
134138
export class LangiumParser extends AbstractLangiumParser {
135139
private readonly linker: Linker;
136140
private readonly converter: ValueConverter;
@@ -160,7 +164,7 @@ export class LangiumParser extends AbstractLangiumParser {
160164
return ruleMethod;
161165
}
162166

163-
parse<T extends AstNode = AstNode>(input: string, options: { rule?: string } = {}): ParseResult<T> {
167+
parse<T extends AstNode = AstNode>(input: string, options: ParserOptions = {}): ParseResult<T> {
164168
this.nodeBuilder.buildRootNode(input);
165169
const lexerResult = this.lexer.tokenize(input);
166170
this.wrapper.input = lexerResult.tokens;

packages/langium/src/test/langium-test.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,28 @@ import type { LangiumCoreServices, LangiumSharedCoreServices } from '../services
99
import type { AstNode, CstNode, Properties } from '../syntax-tree.js';
1010
import { type LangiumDocument, TextDocument } from '../workspace/documents.js';
1111
import type { BuildOptions } from '../workspace/document-builder.js';
12+
import type { AsyncDisposable } from '../utils/disposable.js';
13+
import type { LangiumServices, LangiumSharedLSPServices } from '../lsp/lsp-services.js';
14+
import type { ParserOptions } from '../parser/langium-parser.js';
1215
import { DiagnosticSeverity, MarkupContent } from 'vscode-languageserver-types';
1316
import { escapeRegExp } from '../utils/regexp-utils.js';
1417
import { URI } from '../utils/uri-utils.js';
1518
import { findNodeForProperty } from '../utils/grammar-utils.js';
1619
import { SemanticTokensDecoder } from '../lsp/semantic-token-provider.js';
1720
import * as assert from 'node:assert';
1821
import { stream } from '../utils/stream.js';
19-
import type { AsyncDisposable } from '../utils/disposable.js';
2022
import { Disposable } from '../utils/disposable.js';
2123
import { normalizeEOL } from '../generate/template-string.js';
22-
import type { LangiumServices, LangiumSharedLSPServices } from '../lsp/lsp-services.js';
2324

2425
export interface ParseHelperOptions extends BuildOptions {
2526
/**
2627
* Specifies the URI of the generated document. Will use a counter variable if not specified.
2728
*/
2829
documentUri?: string;
30+
/**
31+
* Options passed to the LangiumParser.
32+
*/
33+
parserOptions?: ParserOptions
2934
}
3035

3136
let nextDocumentId = 1;
@@ -35,7 +40,7 @@ export function parseHelper<T extends AstNode = AstNode>(services: LangiumCoreSe
3540
const documentBuilder = services.shared.workspace.DocumentBuilder;
3641
return async (input, options) => {
3742
const uri = URI.parse(options?.documentUri ?? `file:///${nextDocumentId++}${metaData.fileExtensions[0] ?? ''}`);
38-
const document = services.shared.workspace.LangiumDocumentFactory.fromString<T>(input, uri);
43+
const document = services.shared.workspace.LangiumDocumentFactory.fromString<T>(input, uri, options?.parserOptions);
3944
services.shared.workspace.LangiumDocuments.addDocument(document);
4045
await documentBuilder.build([document], options);
4146
return document;

packages/langium/src/workspace/documents.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export { TextDocument } from 'vscode-languageserver-textdocument';
1515

1616
import type { Diagnostic, Range } from 'vscode-languageserver-types';
1717
import type { FileSystemProvider } from './file-system-provider.js';
18-
import type { ParseResult } from '../parser/langium-parser.js';
18+
import type { ParseResult, ParserOptions } from '../parser/langium-parser.js';
1919
import type { ServiceRegistry } from '../service-registry.js';
2020
import type { LangiumSharedCoreServices } from '../services.js';
2121
import type { AstNode, AstNodeDescription, Mutable, Reference } from '../syntax-tree.js';
@@ -126,7 +126,7 @@ export interface LangiumDocumentFactory {
126126
/**
127127
* Create a Langium document from a `TextDocument` (usually associated with a file).
128128
*/
129-
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri?: URI): LangiumDocument<T>;
129+
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri?: URI, options?: ParserOptions): LangiumDocument<T>;
130130
/**
131131
* Create a Langium document from a `TextDocument` asynchronously. This action can be cancelled if a cancellable parser implementation has been provided.
132132
*/
@@ -135,7 +135,7 @@ export interface LangiumDocumentFactory {
135135
/**
136136
* Create an Langium document from an in-memory string.
137137
*/
138-
fromString<T extends AstNode = AstNode>(text: string, uri: URI): LangiumDocument<T>;
138+
fromString<T extends AstNode = AstNode>(text: string, uri: URI, options?: ParserOptions): LangiumDocument<T>;
139139
/**
140140
* Create a Langium document from an in-memory string asynchronously. This action can be cancelled if a cancellable parser implementation has been provided.
141141
*/
@@ -178,42 +178,42 @@ export class DefaultLangiumDocumentFactory implements LangiumDocumentFactory {
178178
return this.createAsync<T>(uri, content, cancellationToken);
179179
}
180180

181-
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri?: URI): LangiumDocument<T>;
181+
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri?: URI, options?: ParserOptions): LangiumDocument<T>;
182182
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri: URI | undefined, cancellationToken: CancellationToken): Promise<LangiumDocument<T>>;
183-
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri?: URI, cancellationToken?: CancellationToken): LangiumDocument<T> | Promise<LangiumDocument<T>> {
183+
fromTextDocument<T extends AstNode = AstNode>(textDocument: TextDocument, uri?: URI, token?: CancellationToken | ParserOptions): LangiumDocument<T> | Promise<LangiumDocument<T>> {
184184
uri = uri ?? URI.parse(textDocument.uri);
185-
if (cancellationToken) {
186-
return this.createAsync<T>(uri, textDocument, cancellationToken);
185+
if (CancellationToken.is(token)) {
186+
return this.createAsync<T>(uri, textDocument, token);
187187
} else {
188-
return this.create<T>(uri, textDocument);
188+
return this.create<T>(uri, textDocument, token);
189189
}
190190
}
191191

192-
fromString<T extends AstNode = AstNode>(text: string, uri: URI): LangiumDocument<T>;
192+
fromString<T extends AstNode = AstNode>(text: string, uri: URI, options?: ParserOptions): LangiumDocument<T>;
193193
fromString<T extends AstNode = AstNode>(text: string, uri: URI, cancellationToken: CancellationToken): Promise<LangiumDocument<T>>;
194-
fromString<T extends AstNode = AstNode>(text: string, uri: URI, cancellationToken?: CancellationToken): LangiumDocument<T> | Promise<LangiumDocument<T>> {
195-
if (cancellationToken) {
196-
return this.createAsync<T>(uri, text, cancellationToken);
194+
fromString<T extends AstNode = AstNode>(text: string, uri: URI, token?: CancellationToken | ParserOptions): LangiumDocument<T> | Promise<LangiumDocument<T>> {
195+
if (CancellationToken.is(token)) {
196+
return this.createAsync<T>(uri, text, token);
197197
} else {
198-
return this.create<T>(uri, text);
198+
return this.create<T>(uri, text, token);
199199
}
200200
}
201201

202202
fromModel<T extends AstNode = AstNode>(model: T, uri: URI): LangiumDocument<T> {
203203
return this.create<T>(uri, { $model: model });
204204
}
205205

206-
protected create<T extends AstNode = AstNode>(uri: URI, content: string | TextDocument | { $model: T }): LangiumDocument<T> {
206+
protected create<T extends AstNode = AstNode>(uri: URI, content: string | TextDocument | { $model: T }, options?: ParserOptions): LangiumDocument<T> {
207207
if (typeof content === 'string') {
208-
const parseResult = this.parse<T>(uri, content);
208+
const parseResult = this.parse<T>(uri, content, options);
209209
return this.createLangiumDocument<T>(parseResult, uri, undefined, content);
210210

211211
} else if ('$model' in content) {
212212
const parseResult = { value: content.$model, parserErrors: [], lexerErrors: [] };
213213
return this.createLangiumDocument<T>(parseResult, uri);
214214

215215
} else {
216-
const parseResult = this.parse<T>(uri, content.getText());
216+
const parseResult = this.parse<T>(uri, content.getText(), options);
217217
return this.createLangiumDocument(parseResult, uri, content);
218218
}
219219
}
@@ -300,9 +300,9 @@ export class DefaultLangiumDocumentFactory implements LangiumDocumentFactory {
300300
return document;
301301
}
302302

303-
protected parse<T extends AstNode>(uri: URI, text: string): ParseResult<T> {
303+
protected parse<T extends AstNode>(uri: URI, text: string, options?: ParserOptions): ParseResult<T> {
304304
const services = this.serviceRegistry.getServices(uri);
305-
return services.parser.LangiumParser.parse<T>(text);
305+
return services.parser.LangiumParser.parse<T>(text, options);
306306
}
307307

308308
protected parseAsync<T extends AstNode>(uri: URI, text: string, cancellationToken: CancellationToken): Promise<ParseResult<T>> {

packages/langium/test/parser/langium-parser.test.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,57 +4,68 @@
44
* terms of the MIT License, which is available in the project root.
55
******************************************************************************/
66

7-
import type { LangiumParser } from 'langium';
7+
import type { AstNode, LangiumCoreServices } from 'langium';
88
import { describe, expect, test, beforeEach } from 'vitest';
9-
import { createServicesForGrammar } from 'langium/grammar';
9+
import { createServicesForGrammar } from 'langium/grammar';
10+
import { parseHelper } from 'langium/test';
1011

1112
describe('Partial parsing', () => {
1213
const content = `
1314
grammar Test
14-
entry Model: (a+=A | b+=B)*;
15+
entry Model: 'model' (a+=A | b+=B)*;
1516
A: 'a' name=ID;
1617
B: 'b' name=ID;
1718
terminal ID: /[_a-zA-Z][\\w_]*/;
1819
hidden terminal WS: /\\s+/;
1920
`;
2021

21-
let parser: LangiumParser;
22+
let services: LangiumCoreServices;
2223

2324
beforeEach(async () => {
24-
parser = await parserFromGrammar(content);
25+
services = await createServicesForGrammar({ grammar: content });
2526
});
2627

2728
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2829
function expectCorrectParse(text: string, rule?: string): any {
29-
const result = parser.parse(text, { rule });
30+
const result = services.parser.LangiumParser.parse(text, { rule });
3031
expect(result.parserErrors.length).toBe(0);
3132
return result.value;
3233
}
3334

3435
function expectErrorneousParse(text: string, rule?: string): void {
35-
const result = parser.parse(text, { rule });
36+
const result = services.parser.LangiumParser.parse(text, { rule });
3637
expect(result.parserErrors.length).toBeGreaterThan(0);
3738
}
3839

3940
test('Should parse correctly with normal entry rule', () => {
40-
const result = expectCorrectParse('a Foo b Bar');
41+
const result = expectCorrectParse('model a Foo b Bar');
4142
expect(result.a[0].name).toEqual('Foo');
4243
expect(result.b[0].name).toEqual('Bar');
4344
});
4445

4546
test('Should parse correctly with alternative entry rule A', () => {
4647
const result = expectCorrectParse('a Foo', 'A');
4748
expect(result.name).toEqual('Foo');
49+
expectErrorneousParse('model a Foo', 'A');
4850
expectErrorneousParse('b Bar', 'A');
4951
});
5052

5153
test('Should parse correctly with alternative entry rule B', () => {
5254
const result = expectCorrectParse('b Foo', 'B');
5355
expect(result.name).toEqual('Foo');
56+
expectErrorneousParse('model b Foo', 'B');
5457
expectErrorneousParse('a Foo', 'B');
5558
});
59+
60+
test('Parse helper supports using alternative entry rule A', async () => {
61+
const parse = parseHelper<A>(services);
62+
const document = await parse('a Foo', { parserOptions: { rule: 'A' } });
63+
expect(document.parseResult.parserErrors.length).toBe(0);
64+
expect(document.parseResult.value.name).toEqual('Foo');
65+
});
66+
5667
});
5768

58-
async function parserFromGrammar(grammar: string): Promise<LangiumParser> {
59-
return (await createServicesForGrammar({ grammar })).parser.LangiumParser;
69+
interface A extends AstNode {
70+
name: string
6071
}

0 commit comments

Comments
 (0)