Skip to content

Commit 5321655

Browse files
committed
generate a partial ast
1 parent 5e942c8 commit 5321655

File tree

5 files changed

+67
-19
lines changed

5 files changed

+67
-19
lines changed

packages/langium-cli/langium-config-schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@
172172
"description": "A flag to determine whether langium uses itself to bootstrap",
173173
"type": "boolean"
174174
},
175-
"optionalProperties": {
176-
"description": "Use optional properties in generated ast (except for boolean and arrays properties)",
175+
"generatePartialAst": {
176+
"description": "Generate a partial AST with optional properties (usefull to access the parsed AST with an incomplete document)",
177177
"type": "boolean"
178178
}
179179
},

packages/langium-cli/src/generate.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { loadConfig } from './package.js';
1111
import { AstUtils, GrammarAST } from 'langium';
1212
import { createLangiumGrammarServices, resolveImport, resolveImportUri, resolveTransitiveImports } from 'langium/grammar';
1313
import { NodeFileSystem } from 'langium/node';
14-
import { generateAst } from './generator/ast-generator.js';
14+
import { generateAst, generateAstPartial } from './generator/ast-generator.js';
1515
import { serializeGrammar } from './generator/grammar-serializer.js';
1616
import { generateModule } from './generator/module-generator.js';
1717
import { generateBnf } from './generator/bnf-generator.js';
@@ -327,7 +327,7 @@ export async function runGenerator(config: LangiumConfig, options: GenerateOptio
327327
const output = path.resolve(relPath, config.out ?? 'src/generated');
328328
log('log', options, `Writing generated files to ${chalk.white.bold(output)}`);
329329

330-
if (await rmdirWithFail(output, ['ast.ts', 'grammar.ts', 'module.ts'], options)) {
330+
if (await rmdirWithFail(output, ['ast.ts', 'ast-partial.ts', 'grammar.ts', 'module.ts'], options)) {
331331
return buildResult(false);
332332
}
333333
if (await mkdirWithFail(output, options)) {
@@ -336,7 +336,10 @@ export async function runGenerator(config: LangiumConfig, options: GenerateOptio
336336

337337
const genAst = generateAst(grammarServices, embeddedGrammars, config);
338338
await writeWithFail(path.resolve(updateLangiumInternalAstPath(output, config), 'ast.ts'), genAst, options);
339-
339+
if(config.generatePartialAst) {
340+
const genAstPartial = generateAstPartial(grammarServices, embeddedGrammars, config);
341+
await writeWithFail(path.resolve(updateLangiumInternalAstPath(output, config), 'ast-partial.ts'), genAstPartial, options);
342+
}
340343
const serializedGrammar = serializeGrammar(grammarServices, embeddedGrammars, config);
341344
await writeWithFail(path.resolve(output, 'grammar.ts'), serializedGrammar, options);
342345

packages/langium-cli/src/generator/ast-generator.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { collectAst, collectTypeHierarchy, findReferenceTypes, isAstType, mergeT
1212
import { generatedHeader } from './node-util.js';
1313
import { collectKeywords, collectTerminalRegexps } from './langium-util.js';
1414

15-
export function generateAst(services: LangiumCoreServices, grammars: Grammar[], config: LangiumConfig): string {
15+
export function generateAst(services: LangiumCoreServices, grammars: Grammar[], config: LangiumConfig,): string {
1616
const astTypes = collectAst(grammars, services.shared.workspace.LangiumDocuments);
1717
const importFrom = config.langiumInternal ? `../../syntax-tree${config.importExtension}` : 'langium';
1818

@@ -26,7 +26,7 @@ export function generateAst(services: LangiumCoreServices, grammars: Grammar[],
2626
${generateTerminalConstants(grammars, config)}
2727
2828
${joinToNode(astTypes.unions, union => union.toAstTypesString(isAstType(union.type)), { appendNewLineIfNotEmpty: true })}
29-
${joinToNode(astTypes.interfaces, iFace => iFace.toAstTypesString(true, config.optionalProperties), { appendNewLineIfNotEmpty: true })}
29+
${joinToNode(astTypes.interfaces, iFace => iFace.toAstTypesString(true, false), { appendNewLineIfNotEmpty: true })}
3030
${
3131
astTypes.unions = astTypes.unions.filter(e => isAstType(e.type)),
3232
generateAstReflection(config, astTypes)
@@ -35,6 +35,30 @@ export function generateAst(services: LangiumCoreServices, grammars: Grammar[],
3535
return toString(fileNode);
3636
/* eslint-enable @typescript-eslint/indent */
3737
}
38+
export function generateAstPartial(services: LangiumCoreServices, grammars: Grammar[], config: LangiumConfig,): string {
39+
const astTypes = collectAst(grammars, services.shared.workspace.LangiumDocuments);
40+
const importFrom = config.langiumInternal ? `../../syntax-tree${config.importExtension}` : 'langium';
41+
42+
/* eslint-disable @typescript-eslint/indent */
43+
const fileNode = expandToNode`
44+
${generatedHeader}
45+
46+
/* eslint-disable */
47+
import * as langium from '${importFrom}';
48+
import * as ast from './ast.js';
49+
50+
${generateTerminalConstantsPartial(grammars, config)}
51+
52+
${joinToNode(astTypes.unions, union => union.toAstTypesString(isAstType(union.type), true), { appendNewLineIfNotEmpty: true })}
53+
${joinToNode(astTypes.interfaces, iFace => iFace.toAstTypesString(true, true), { appendNewLineIfNotEmpty: true })}
54+
${
55+
astTypes.unions = astTypes.unions.filter(e => isAstType(e.type)),
56+
generateAstReflectionPartial(config, astTypes)
57+
}
58+
`;
59+
return toString(fileNode);
60+
/* eslint-enable @typescript-eslint/indent */
61+
}
3862

3963
function generateAstReflection(config: LangiumConfig, astTypes: AstTypes): Generated {
4064
const typeNames: string[] = astTypes.interfaces.map(t => t.name)
@@ -69,6 +93,14 @@ function generateAstReflection(config: LangiumConfig, astTypes: AstTypes): Gener
6993
`.appendNewLine();
7094
}
7195

96+
function generateAstReflectionPartial(config: LangiumConfig, _astTypes: AstTypes): Generated {
97+
98+
return expandToNode`
99+
100+
export type { ${config.projectName}AstType, ${config.projectName}AstReflection } from './ast.js';
101+
export const reflection = ast.reflection;
102+
`.appendNewLine();
103+
}
72104
function buildTypeMetaDataMethod(astTypes: AstTypes): Generated {
73105
/* eslint-disable @typescript-eslint/indent */
74106
return expandToNode`
@@ -253,3 +285,9 @@ function generateTerminalConstants(grammars: Grammar[], config: LangiumConfig):
253285
export type ${config.projectName}TokenNames = ${config.projectName}TerminalNames | ${config.projectName}KeywordNames;
254286
`.appendNewLine();
255287
}
288+
289+
function generateTerminalConstantsPartial(grammars: Grammar[], config: LangiumConfig): Generated {
290+
return expandToNode`
291+
export { ${config.projectName}Terminals, type ${config.projectName}TerminalNames, type ${config.projectName}KeywordNames, type ${config.projectName}TokenNames } from './ast.js';
292+
`.appendNewLine();
293+
}

packages/langium-cli/src/package-types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export interface LangiumConfig {
3030
chevrotainParserConfig?: IParserConfig,
3131
/** The following option is meant to be used only by Langium itself */
3232
langiumInternal?: boolean
33-
/** Use optional properties in generated ast (except for boolean and arrays properties) */
34-
optionalProperties?: boolean
33+
/** Generate a partial AST with optional properties (usefull to access the parsed AST with an incomplete document) */
34+
generatePartialAst?: boolean
3535
}
3636

3737
export interface LangiumLanguageConfig {

packages/langium/src/grammar/type-system/type-collector/types.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,14 @@ export class UnionType {
121121
this.dataType = options?.dataType;
122122
}
123123

124-
toAstTypesString(reflectionInfo: boolean): string {
124+
toAstTypesString(reflectionInfo: boolean, isPartial?: boolean): string {
125125
const unionNode = expandToNode`
126126
export type ${this.name} = ${propertyTypeToString(this.type, 'AstType')};
127127
`.appendNewLine();
128128

129129
if (reflectionInfo) {
130130
unionNode.appendNewLine()
131-
.append(addReflectionInfo(this.name));
131+
.append(addReflectionInfo(this.name, isPartial));
132132
}
133133

134134
if (this.dataType) {
@@ -218,7 +218,7 @@ export class InterfaceType {
218218
this.abstract = abstract;
219219
}
220220

221-
toAstTypesString(reflectionInfo: boolean, optionalProperties?: boolean): string {
221+
toAstTypesString(reflectionInfo: boolean, isPartial?: boolean): string {
222222
const interfaceSuperTypes = this.interfaceSuperTypes.map(e => e.name);
223223
const superTypes = interfaceSuperTypes.length > 0 ? distinctAndSorted([...interfaceSuperTypes]) : ['langium.AstNode'];
224224
const interfaceNode = expandToNode`
@@ -233,15 +233,15 @@ export class InterfaceType {
233233
body.append(`readonly $type: ${distinctAndSorted([...this.typeNames]).map(e => `'${e}'`).join(' | ')};`).appendNewLine();
234234
}
235235
body.append(
236-
pushProperties(this.properties, 'AstType', optionalProperties)
236+
pushProperties(this.properties, 'AstType', isPartial)
237237
);
238238
});
239239
interfaceNode.append('}').appendNewLine();
240240

241241
if (reflectionInfo) {
242242
interfaceNode
243243
.appendNewLine()
244-
.append(addReflectionInfo(this.name));
244+
.append(addReflectionInfo(this.name, isPartial));
245245
}
246246

247247
return toString(interfaceNode);
@@ -408,13 +408,13 @@ function typeParenthesis(type: PropertyType, name: string): string {
408408
function pushProperties(
409409
properties: Property[],
410410
mode: 'AstType' | 'DeclaredType',
411-
optionalProperties?: boolean,
411+
isPArtial?: boolean,
412412
reserved = new Set<string>()
413413
): Generated {
414414

415415
function propertyToString(property: Property): string {
416416
const name = mode === 'AstType' ? property.name : escapeReservedWords(property.name, reserved);
417-
const optional = !isMandatoryPropertyType(property.type) && (property.optional || optionalProperties);
417+
const optional = !isMandatoryPropertyType(property.type) && property.defaultValue === undefined && (property.optional || isPArtial);
418418
const propType = propertyTypeToString(property.type, mode);
419419
return `${name}${optional ? '?' : ''}: ${propType};`;
420420
}
@@ -441,14 +441,21 @@ export function isMandatoryPropertyType(propertyType: PropertyType): boolean {
441441
}
442442
}
443443

444-
function addReflectionInfo(name: string): Generated {
445-
return expandToNode`
444+
function addReflectionInfo(name: string, isPartial?: boolean): Generated {
445+
return (isPartial ?
446+
expandToNode`
447+
export const ${name} = ast.${name};
448+
449+
export function is${name}(item: unknown): item is ${name} {
450+
return reflection.isInstance(item, ${name});
451+
}
452+
`: expandToNode`
446453
export const ${name} = '${name}';
447454
448455
export function is${name}(item: unknown): item is ${name} {
449456
return reflection.isInstance(item, ${name});
450457
}
451-
`.appendNewLine();
458+
`).appendNewLine();
452459
}
453460

454461
function addDataTypeReflectionInfo(union: UnionType): Generated {

0 commit comments

Comments
 (0)