blob: 2eec6722225a358308b70f98974f6c1d4e43a6d2 [file] [log] [blame]
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {SubmitRequirementExpressionInfo} from '../api/rest-api';
export enum SubmitRequirementExpressionAtomStatus {
UNKNOWN = 'UNKNOWN',
PASSING = 'PASSING',
FAILING = 'FAILING',
}
export interface SubmitRequirementExpressionPart {
value: string;
isAtom: boolean;
// Defined iff isAtom is true.
atomStatus?: SubmitRequirementExpressionAtomStatus;
}
interface AtomMatch {
start: number;
end: number;
isPassing: boolean;
}
function appendAllOccurrences(
text: string,
match: string,
isPassing: boolean,
matchedAtoms: AtomMatch[]
) {
for (let searchStartIndex = 0; ; ) {
let index = text.indexOf(match, searchStartIndex);
if (index === -1) {
break;
}
searchStartIndex = index + match.length;
// Check for unary minus *before* adding the match
let atomIsPassing = isPassing; // Use a local variable
if (index !== 0 && text[index - 1] === '-') {
--index;
atomIsPassing = !isPassing; // Negate only for this occurrence
}
matchedAtoms.push({
start: index,
end: searchStartIndex,
isPassing: atomIsPassing,
});
}
}
function splitExpressionIntoParts(
expression: string,
matchedAtoms: AtomMatch[]
): SubmitRequirementExpressionPart[] {
const result: SubmitRequirementExpressionPart[] = [];
let currentIndex = 0;
for (const {start, end, isPassing} of matchedAtoms) {
// We don't handle overlapping matches, but this can happen.
if (start < currentIndex) continue;
if (start > currentIndex) {
result.push({
value: expression.slice(currentIndex, start),
isAtom: false,
});
}
result.push({
value: expression.slice(start, end),
isAtom: true,
atomStatus: isPassing
? SubmitRequirementExpressionAtomStatus.PASSING
: SubmitRequirementExpressionAtomStatus.FAILING,
});
currentIndex = end;
}
if (currentIndex < expression.length) {
result.push({
value: expression.slice(currentIndex),
isAtom: false,
});
}
return result;
}
/**
* Returns expression string split into ExpressionPart.
*
* Concatenation result of all parts is equal to original expression string.
*
* Unary minus is included in the atom and is accounted in the status.
*/
export function atomizeExpression(
expression: SubmitRequirementExpressionInfo
): SubmitRequirementExpressionPart[] {
const matchedAtoms: AtomMatch[] = [];
expression.passing_atoms?.forEach(atom =>
appendAllOccurrences(
expression.expression,
atom,
/* isPassing=*/ true,
matchedAtoms
)
);
expression.failing_atoms?.forEach(atom =>
appendAllOccurrences(
expression.expression,
atom,
/* isPassing=*/ false,
matchedAtoms
)
);
matchedAtoms.sort((a, b) => a.start - b.start);
return splitExpressionIntoParts(expression.expression, matchedAtoms);
}