rdesign/frontend/node_modules/langium/lib/lsp/completion/follow-element-computation.js

312 lines
11 KiB
JavaScript

/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import * as ast from '../../languages/generated/ast.js';
import { getCrossReferenceTerminal, getExplicitRuleType, getTypeName, isArrayCardinality, isOptionalCardinality, terminalRegex } from '../../utils/grammar-utils.js';
import { getContainerOfType } from '../../utils/ast-utils.js';
/**
* Calculates any features that can follow the given feature stack.
* This also includes features following optional features and features from previously called rules that could follow the last feature.
* @param featureStack A stack of features starting at the entry rule and ending at the feature of the current cursor position.
* @param unparsedTokens All tokens which haven't been parsed successfully yet. This is the case when we call this function inside an alternative or before parsing any tokens.
* @returns Any `AbstractElement` that could be following the given feature stack.
*/
export function findNextFeatures(featureStack, unparsedTokens) {
const context = {
stacks: featureStack,
tokens: unparsedTokens
};
interpretTokens(context);
// Reset the container property
context.stacks.flat().forEach(feature => { feature.property = undefined; });
const nextStacks = findNextFeatureStacks(context.stacks);
// We only need the last element of each stack
return nextStacks.map(e => e[e.length - 1]);
}
function findNextFeaturesInternal(stack) {
const features = [];
if (stack.length === 0) {
return features;
}
const top = stack[stack.length - 1];
const { group, child } = findGroupAndChild(top.feature);
if (isArrayCardinality(child.cardinality)) {
// The feature can appear again, so we try to find its first features again
const repeatingFeatures = findFirstFeaturesInternal({
feature: child,
type: top.type,
property: top.property
});
features.push(...repeatingFeatures);
}
let groupIndex = -1;
if (group) {
groupIndex = group.elements.indexOf(child);
}
const parentStack = stack.slice(0, -1);
if (!group) {
const parent = getAbstractElementParent(child);
if (parent) {
// The feature is not part of a group
// But the parent might be
features.push(...findNextFeaturesInternal([
...parentStack,
{
feature: parent,
}
]));
}
else {
// The feature is "standalone", meaning it is the top-level feature of a rule
// The next elements are defined by the previous stack elements
features.push(...findNextFeaturesInternal(parentStack));
}
}
else {
// The feature is somewhere within the group
const nextIndex = groupIndex + 1;
const { stacks, end } = findNextFeaturesInGroup({
feature: group,
type: top.type
}, nextIndex);
for (const newStack of stacks) {
features.push([...parentStack, ...newStack]);
}
// If we reached the end of the group, continue searching for following features
if (end) {
// Set groupIndex to the last element index to indicate that we are at the end of the group
// This will trigger the parent search below
groupIndex = group.elements.length - 1;
}
}
if (group && groupIndex === group.elements.length - 1) {
// The feature is the last element of a group
// The next elements are defined by the parent group
features.push(...findNextFeaturesInternal([
...parentStack,
{
feature: group,
}
]));
}
return features;
}
/**
* Calculates the first child feature of any `AbstractElement`.
* @param next The `AbstractElement` whose first child features should be calculated.
*/
export function findFirstFeatures(next) {
return findFirstFeaturesInternal({ feature: next });
}
function findFirstFeaturesInternal(next) {
const { feature, type } = next;
if (ast.isGroup(feature)) {
return findNextFeaturesInGroup(next, 0).stacks;
}
else if (ast.isAlternatives(feature) || ast.isUnorderedGroup(feature)) {
return feature.elements.flatMap(e => findFirstFeaturesInternal({
feature: e,
type,
property: next.property
}));
}
else if (ast.isAssignment(feature)) {
return findFirstFeaturesInternal({
feature: feature.terminal,
type,
property: next.property ?? feature.feature
});
}
else if (ast.isAction(feature)) {
return findNextFeaturesInternal([{
feature,
type: getTypeName(feature),
property: next.property ?? feature.feature
}]);
}
else if (ast.isRuleCall(feature) && ast.isParserRule(feature.rule.ref)) {
const rule = feature.rule.ref;
const stacks = findFirstFeaturesInternal({
feature: rule.definition,
type: rule.fragment || rule.dataType ? undefined : (getExplicitRuleType(rule) ?? rule.name),
property: next.property
});
for (const stack of stacks) {
stack.unshift(next);
}
return stacks;
}
else if (ast.isRuleCall(feature) && ast.isInfixRule(feature.rule.ref)) {
const rule = feature.rule.ref;
const call = rule.call.rule.ref;
if (!ast.isParserRule(call)) {
console.error('Failed to resolve reference to ' + rule.call.rule.$refText);
return [];
}
const stacks = findFirstFeaturesInternal({
feature: call.definition,
type: getExplicitRuleType(call) ?? call.name,
property: 'parts'
});
for (const stack of stacks) {
stack.unshift(next);
}
return stacks;
}
else {
return [[next]];
}
}
function findNextFeaturesInGroup(next, index) {
const features = [];
let firstFeature;
while (index < next.feature.elements.length) {
const feature = next.feature.elements[index];
firstFeature = {
feature,
type: next.type
};
const stacks = findFirstFeaturesInternal(firstFeature);
features.push(...stacks);
if (isElementOptional(feature, new Map())) {
// Continue with the next element
index++;
}
else {
break;
}
}
return {
stacks: features,
end: index >= next.feature.elements.length
};
}
/**
* Determines recursively whether an element is optional. It is not sufficient to check only the cardinality of the element itself,
* because it could a group or alternative that contains only optional elements.
*/
function isElementOptional(feature, visited) {
const visitedResult = visited.get(feature);
if (visitedResult !== undefined) {
return visitedResult;
}
visited.set(feature, false);
if (isOptionalCardinality(feature.cardinality, feature)) {
visited.set(feature, true);
return true;
}
if (ast.isGroup(feature)) {
for (const element of feature.elements) {
if (!isElementOptional(element, visited)) {
visited.set(feature, false);
return false;
}
}
visited.set(feature, true);
return true;
}
else if (ast.isAlternatives(feature) || ast.isUnorderedGroup(feature)) {
for (const element of feature.elements) {
if (isElementOptional(element, visited)) {
visited.set(feature, true);
return true;
}
}
visited.set(feature, false);
return false;
}
else if (ast.isRuleCall(feature) && ast.isParserRule(feature.rule.ref)) {
const rule = feature.rule.ref;
const result = isElementOptional(rule.definition, visited);
visited.set(feature, result);
return result;
}
return false;
}
function interpretTokens(context) {
for (const token of context.tokens) {
const nextFeatureStacks = findNextFeatureStacks(context.stacks, token);
context.stacks = nextFeatureStacks;
}
}
function findNextFeatureStacks(stacks, token) {
const newStacks = [];
for (const stack of stacks) {
newStacks.push(...interpretStackToken(stack, token));
}
return newStacks;
}
function interpretStackToken(stack, token) {
const allNextFeatures = findNextFeaturesInternal(stack);
const matchingNextFeatures = allNextFeatures.filter(next => token ? featureMatches(next[next.length - 1].feature, token) : true);
return matchingNextFeatures;
}
export function findGroupAndChild(feature) {
let parent;
let item = feature;
while (item.$container) {
if (ast.isGroup(item.$container)) {
parent = item.$container;
break;
}
else if (ast.isAbstractElement(item.$container)) {
item = item.$container;
}
else {
break;
}
}
if (parent) {
return { group: parent, child: item };
}
return {
group: undefined,
// Even if there is no group, return the feature parent if it is an assignment
// We need this later to handle the cardinality of the feature correctly
child: getContainerOfType(feature, ast.isAssignment) ?? feature
};
}
function getAbstractElementParent(element) {
const parent = element.$container;
const assignment = getContainerOfType(parent, ast.isAssignment);
if (assignment) {
return getAbstractElementParent(assignment);
}
else {
if (parent && ast.isAbstractElement(parent)) {
return parent;
}
}
return undefined;
}
function featureMatches(feature, token) {
if (ast.isKeyword(feature)) {
const content = feature.value;
return content === token.tokenType.name;
}
else if (ast.isRuleCall(feature)) {
return ruleMatches(feature.rule.ref, token);
}
else if (ast.isCrossReference(feature)) {
const crossRefTerminal = getCrossReferenceTerminal(feature);
if (crossRefTerminal) {
return featureMatches(crossRefTerminal, token);
}
}
return false;
}
function ruleMatches(rule, token) {
if (ast.isParserRule(rule)) {
const ruleFeatures = findFirstFeatures(rule.definition);
return ruleFeatures.some(e => featureMatches(e[e.length - 1].feature, token));
}
else if (ast.isTerminalRule(rule)) {
return terminalRegex(rule).test(token.image);
}
else {
return false;
}
}
//# sourceMappingURL=follow-element-computation.js.map