Skip to content

Commit

Permalink
Perform type refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew committed Feb 10, 2023
1 parent 333b79c commit 49973f6
Show file tree
Hide file tree
Showing 32 changed files with 2,259 additions and 1,857 deletions.
2 changes: 1 addition & 1 deletion examples/arithmetics/src/language-server/generated/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class ArithmeticsAstReflection extends AbstractAstReflection {
return this.isSubtype(AbstractDefinition, supertype);
}
case Definition: {
return this.isSubtype(Statement, supertype) || this.isSubtype(AbstractDefinition, supertype);
return this.isSubtype(AbstractDefinition, supertype) || this.isSubtype(Statement, supertype);
}
case Evaluation: {
return this.isSubtype(Statement, supertype);
Expand Down
35 changes: 17 additions & 18 deletions examples/arithmetics/src/language-server/generated/grammar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,25 +588,24 @@ export const ArithmeticsGrammar = (): Grammar => loadedArithmeticsGrammar ?? (lo
"types": [
{
"$type": "Type",
"typeAlternatives": [
{
"$type": "AtomType",
"refType": {
"$ref": "#/rules@2"
},
"isArray": false,
"isRef": false
},
{
"$type": "AtomType",
"refType": {
"$ref": "#/rules@3"
"name": "AbstractDefinition",
"type": {
"$type": "UnionType",
"types": [
{
"$type": "SimpleType",
"typeRef": {
"$ref": "#/rules@2"
}
},
"isArray": false,
"isRef": false
}
],
"name": "AbstractDefinition"
{
"$type": "SimpleType",
"typeRef": {
"$ref": "#/rules@3"
}
}
]
}
}
],
"definesHiddenTokens": false,
Expand Down
4 changes: 2 additions & 2 deletions examples/arithmetics/syntaxes/arithmetics.monarch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ export default {
'def','module'
],
operators: [
'-',',',';',':','*','/','+'
'*','+',',','-','/',':',';'
],
symbols: /-|,|;|:|\(|\)|\*|/|\+/,
symbols: /\(|\)|\*|\+|,|-|/|:|;/,

tokenizer: {
initial: [
Expand Down
4 changes: 2 additions & 2 deletions examples/domainmodel/syntaxes/domainmodel.monarch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ export default {
'datatype','entity','extends','many','package'
],
operators: [
':','.'
'.',':'
],
symbols: /:|\.|\{|\}/,
symbols: /\.|:|\{|\}/,

tokenizer: {
initial: [
Expand Down
2 changes: 1 addition & 1 deletion examples/statemachine/syntaxes/statemachine.monarch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default {
operators: [
'=>'
],
symbols: /\{|\}|=>/,
symbols: /=>|\{|\}/,

tokenizer: {
initial: [
Expand Down
42 changes: 17 additions & 25 deletions packages/langium-cli/src/generator/ast-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import {
GeneratorNode, Grammar, IndentNode, CompositeGeneratorNode, NL, toString, streamAllContents, MultiMap, LangiumServices, GrammarAST
} from 'langium';
import { AstTypes, collectAllProperties, collectAst, Property } from 'langium/lib/grammar/type-system';
import { AstTypes, collectAst, collectTypeHierarchy, findReferenceTypes, hasArrayType, hasBooleanType, mergeTypesAndInterfaces, Property } from 'langium/lib/grammar/type-system';
import { LangiumConfig } from '../package';
import { generatedHeader } from './util';

Expand All @@ -24,8 +24,8 @@ export function generateAst(services: LangiumServices, grammars: Grammar[], conf
`import { AstNode, AbstractAstReflection${crossRef ? ', Reference' : ''}, ReferenceInfo, TypeMetaData } from '${importFrom}';`, NL, NL
);

astTypes.unions.forEach(union => fileNode.append(union.toAstTypesString(), NL));
astTypes.interfaces.forEach(iFace => fileNode.append(iFace.toAstTypesString(), NL));
astTypes.unions.forEach(union => fileNode.append(union.toAstTypesString(true), NL));
astTypes.interfaces.forEach(iFace => fileNode.append(iFace.toAstTypesString(true), NL));

astTypes.unions = astTypes.unions.filter(e => e.reflection);
fileNode.append(generateAstReflection(config, astTypes));
Expand Down Expand Up @@ -84,11 +84,10 @@ function buildTypeMetaDataMethod(astTypes: AstTypes): GeneratorNode {
const typeSwitchNode = new IndentNode();
typeSwitchNode.append('switch (type) {', NL);
typeSwitchNode.indent(caseNode => {
const allProperties = collectAllProperties(astTypes.interfaces);
for (const interfaceType of astTypes.interfaces) {
const props = allProperties.get(interfaceType.name)!;
const arrayProps = props.filter(e => e.typeAlternatives.some(e => e.array));
const booleanProps = props.filter(e => e.typeAlternatives.every(e => !e.array && e.types.includes('boolean')));
const props = interfaceType.properties;
const arrayProps = props.filter(e => hasArrayType(e.type));
const booleanProps = props.filter(e => hasBooleanType(e.type));
if (arrayProps.length > 0 || booleanProps.length > 0) {
caseNode.append(`case '${interfaceType.name}': {`, NL);
caseNode.indent(caseContent => {
Expand Down Expand Up @@ -174,18 +173,19 @@ function buildCrossReferenceTypes(astTypes: AstTypes): CrossReferenceType[] {
const crossReferences = new MultiMap<string, CrossReferenceType>();
for (const typeInterface of astTypes.interfaces) {
for (const property of typeInterface.properties.sort((a, b) => a.name.localeCompare(b.name))) {
property.typeAlternatives.filter(e => e.reference).flatMap(e => e.types).forEach(type =>
const refTypes = findReferenceTypes(property.type);
for (const refType of refTypes) {
crossReferences.add(typeInterface.name, {
type: typeInterface.name,
feature: property.name,
referenceType: type
})
);
referenceType: refType
});
}
}
// Since the types are topologically sorted we can assume
// that all super type properties have already been processed
for (const superType of typeInterface.printingSuperTypes) {
const superTypeCrossReferences = crossReferences.get(superType).map(e => ({
for (const superType of typeInterface.interfaceSuperTypes) {
const superTypeCrossReferences = crossReferences.get(superType.name).map(e => ({
...e,
type: typeInterface.name
}));
Expand All @@ -210,7 +210,7 @@ function buildIsSubtypeMethod(astTypes: AstTypes): GeneratorNode {
switchNode.contents.pop();
switchNode.append(' {', NL);
switchNode.indent(caseNode => {
caseNode.append(`return ${superTypes.split(':').map(e => `this.isSubtype(${e}, supertype)`).join(' || ')};`);
caseNode.append(`return ${superTypes.split(':').sort().map(e => `this.isSubtype(${e}, supertype)`).join(' || ')};`);
});
switchNode.append(NL, '}', NL);
}
Expand All @@ -225,19 +225,11 @@ function buildIsSubtypeMethod(astTypes: AstTypes): GeneratorNode {
return methodNode;
}

type ChildToSuper = {
name: string,
realSuperTypes: Set<string>
}

function groupBySupertypes(astTypes: AstTypes): MultiMap<string, string> {
const allTypes: ChildToSuper[] = (astTypes.interfaces as ChildToSuper[])
.concat(astTypes.unions)
.filter(e => e.realSuperTypes.size > 0);

const hierarchy = collectTypeHierarchy(mergeTypesAndInterfaces(astTypes));
const superToChild = new MultiMap<string, string>();
for (const item of allTypes) {
superToChild.add([...item.realSuperTypes].join(':'), item.name);
for (const [name, superTypes] of hierarchy.superTypes.entriesGroupedByKey()) {
superToChild.add(superTypes.join(':'), name);
}

return superToChild;
Expand Down
2 changes: 1 addition & 1 deletion packages/langium-cli/src/generator/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function collectKeywords(grammar: Grammar): string[] {
keywords.add(keyword.value);
}

return Array.from(keywords).sort((a, b) => a.localeCompare(b));
return Array.from(keywords).sort();
}

export function getUserInput(text: string): Promise<string> {
Expand Down
120 changes: 65 additions & 55 deletions packages/langium/src/grammar/ast-reflection-interpreter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
* terms of the MIT License, which is available in the project root.
******************************************************************************/

import { AstReflection, isAstNode, ReferenceInfo, TypeMandatoryProperty, TypeMetaData } from '../syntax-tree';
import { AbstractAstReflection, AstReflection, ReferenceInfo, TypeMandatoryProperty, TypeMetaData } from '../syntax-tree';
import { MultiMap } from '../utils/collections';
import { LangiumDocuments } from '../workspace/documents';
import { Grammar, isGrammar } from './generated/ast';
import { collectAst } from './type-system/ast-collector';
import { AstTypes, Property } from './type-system/type-collector/types';
import { collectAllProperties } from './type-system/types-util';
import { collectTypeHierarchy, findReferenceTypes, hasArrayType, hasBooleanType, mergeTypesAndInterfaces } from './type-system/types-util';

export function interpretAstReflection(astTypes: AstTypes): AstReflection;
export function interpretAstReflection(grammar: Grammar, documents?: LangiumDocuments): AstReflection;
Expand All @@ -24,56 +24,78 @@ export function interpretAstReflection(grammarOrTypes: Grammar | AstTypes, docum
const allTypes = collectedTypes.interfaces.map(e => e.name).concat(collectedTypes.unions.map(e => e.name));
const references = buildReferenceTypes(collectedTypes);
const metaData = buildTypeMetaData(collectedTypes);
const superTypeMap = buildSupertypeMap(collectedTypes);
const superTypes = collectTypeHierarchy(mergeTypesAndInterfaces(collectedTypes)).superTypes;

return {
getAllTypes() {
return allTypes;
},
getReferenceType(refInfo: ReferenceInfo): string {
const referenceId = `${refInfo.container.$type}:${refInfo.property}`;
const referenceType = references.get(referenceId);
if (referenceType) {
return referenceType;
}
throw new Error('Could not find reference type for ' + referenceId);
},
getTypeMetaData(type: string): TypeMetaData {
return metaData.get(type) ?? {
name: type,
mandatory: []
};
},
isInstance(node: unknown, type: string): boolean {
return isAstNode(node) && this.isSubtype(node.$type, type);
},
isSubtype(subtype: string, originalSuperType: string): boolean {
if (subtype === originalSuperType) {
return new InterpretedAstReflection({
allTypes,
references,
metaData,
superTypes
});
}

class InterpretedAstReflection extends AbstractAstReflection {

private readonly allTypes: string[];
private readonly references: Map<string, string>;
private readonly metaData: Map<string, TypeMetaData>;
private readonly superTypes: MultiMap<string, string>;

constructor(options: {
allTypes: string[]
references: Map<string, string>
metaData: Map<string, TypeMetaData>
superTypes: MultiMap<string, string>
}) {
super();
this.allTypes = options.allTypes;
this.references = options.references;
this.metaData = options.metaData;
this.superTypes = options.superTypes;
}

getAllTypes(): string[] {
return this.allTypes;
}

getReferenceType(refInfo: ReferenceInfo): string {
const referenceId = `${refInfo.container.$type}:${refInfo.property}`;
const referenceType = this.references.get(referenceId);
if (referenceType) {
return referenceType;
}
throw new Error('Could not find reference type for ' + referenceId);
}

getTypeMetaData(type: string): TypeMetaData {
return this.metaData.get(type) ?? {
name: type,
mandatory: []
};
}

protected computeIsSubtype(subtype: string, originalSuperType: string): boolean {
const superTypes = this.superTypes.get(subtype);
for (const superType of superTypes) {
if (this.isSubtype(superType, originalSuperType)) {
return true;
}
const superTypes = superTypeMap.get(subtype);
for (const superType of superTypes) {
if (this.isSubtype(superType, originalSuperType)) {
return true;
}
}
return false;
}
};
return false;
}

}

function buildReferenceTypes(astTypes: AstTypes): Map<string, string> {
const references = new MultiMap<string, [string, string]>();
for (const interfaceType of astTypes.interfaces) {
for (const property of interfaceType.properties) {
for (const propertyAlternative of property.typeAlternatives) {
if (propertyAlternative.reference) {
references.add(interfaceType.name, [property.name, propertyAlternative.types[0]]);
}
for (const referenceType of findReferenceTypes(property.type)) {
references.add(interfaceType.name, [property.name, referenceType]);
}
}
for (const superType of interfaceType.printingSuperTypes) {
const superTypeReferences = references.get(superType);
for (const superType of interfaceType.interfaceSuperTypes) {
const superTypeReferences = references.get(superType.name);
references.addAll(interfaceType.name, superTypeReferences);
}
}
Expand All @@ -86,11 +108,10 @@ function buildReferenceTypes(astTypes: AstTypes): Map<string, string> {

function buildTypeMetaData(astTypes: AstTypes): Map<string, TypeMetaData> {
const map = new Map<string, TypeMetaData>();
const allProperties = collectAllProperties(astTypes.interfaces);
for (const interfaceType of astTypes.interfaces) {
const props = allProperties.get(interfaceType.name)!;
const arrayProps = props.filter(e => e.typeAlternatives.some(e => e.array));
const booleanProps = props.filter(e => e.typeAlternatives.every(e => !e.array && e.types.includes('boolean')));
const props = interfaceType.superProperties;
const arrayProps = props.filter(e => hasArrayType(e.type));
const booleanProps = props.filter(e => !hasArrayType(e.type) && hasBooleanType(e.type));
if (arrayProps.length > 0 || booleanProps.length > 0) {
map.set(interfaceType.name, {
name: interfaceType.name,
Expand All @@ -113,14 +134,3 @@ function buildMandatoryMetaData(arrayProps: Property[], booleanProps: Property[]
}
return array;
}

function buildSupertypeMap(astTypes: AstTypes): MultiMap<string, string> {
const map = new MultiMap<string, string>();
for (const interfaceType of astTypes.interfaces) {
map.addAll(interfaceType.name, interfaceType.realSuperTypes);
}
for (const unionType of astTypes.unions) {
map.addAll(unionType.name, unionType.realSuperTypes);
}
return map;
}
Loading

0 comments on commit 49973f6

Please sign in to comment.